使用 Office UI 结构在 SharePoint 列表中保存日期值时出错 - React JS

问题描述

这是我第一次尝试使用 office ui fabric react JS 在 SharePoint Online 中开发 Web 部件应用程序。对于一个测试(CRUD 应用程序)项目,我开发了一个在线图书库应用程序。我一直在努力从共享点列表中保存/获取日期值,但由于某种原因出现错误,如下所示:

Error Screen shot

我的 tsx 文件代码如下:

import * as React from 'react';
import styles from './HelloWorld.module.scss';
import { IHelloWorldProps } from './IHelloWorldProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { BookLibListItem } from './BookLibListItem';
import { IBookLibCollection } from './IBookLibCollection';


import { ISPHttpClientOptions,SPHttpClient,SPHttpClientResponse } from '@microsoft/sp-http';


import {
  TextField,autobind,PrimaryButton,DetailsList,DetailsListLayoutMode,CheckBoxVisibility,SelectionMode,Dropdown,DatePicker,IDatePickerStrings,IDropdown,IDropdownoption,ITextFieldStyles,IDropdownStyles,DetailsRowCheck,Selection,DayOfWeek
} from 'office-ui-fabric-react';



const DayPickerStrings: IDatePickerStrings = {
  months: [
    'January','February','march','April','May','June','July','August','September','October','November','December',],shortMonths: ['Jan','Feb','Mar','Apr','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],days: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],shortDays: ['S','M','T','W','F','S'],goToToday: 'Go to today',prevMonthAriaLabel: 'Go to prevIoUs month',nextMonthAriaLabel: 'Go to next month',prevYearariaLabel: 'Go to prevIoUs year',nextYearariaLabel: 'Go to next year',closeButtonAriaLabel: 'Close date picker'
};
/*
export interface IDatePickerBasicExampleState {
  firstDayOfWeek?: DayOfWeek;
}
*/

 // Configure the columns for the DetailsList component
 let _bookListColumns = [
  {
    key: 'ID',name: 'ID',fieldName: 'ID',minWidth: 50,maxWidth: 100,isResizable: true
  },{
    key: 'Title',name: 'Title',fieldName: 'Title',{
    key: 'Author',name: 'Author',fieldName: 'Author',{
    key: 'Publisher',name: 'Publisher',fieldName: 'Publisher',{
    key: 'DateOfPublish',name: 'DateOfPublish',fieldName: 'DateOfPublish',{
    key: 'ISBN',name: 'ISBN',fieldName: 'ISBN',maxWidth: 150,isResizable: true
  }  
];

const textFieldStyles: Partial<ITextFieldStyles> = { fieldGroup: { width: 300 } };
const narrowTextFieldStyles: Partial<ITextFieldStyles> = { fieldGroup: { width: 100 } };
const narrowDropdownStyles: Partial<IDropdownStyles> = { dropdown: { width: 300 } };

function SetDate(props) {
  //const firstDayOfWeek = React.useState(DayOfWeek.Sunday);
  
  return (<DatePicker
{...props.value && moment(props.value).isValid() ? { value: moment(props.value).toDate() } : {}}
className={css(styles.dateFormField,'dateFormField')}
placeholder="Select a date..."
isrequired={props.required}
ariaLabel={props.Title}
parseDateFromString={(dateStr?: string) => { return moment(dateStr,'L').toDate(); }}
formatDate={(date: Date) => (typeof date.toLocaleDateString === 'function') ? date.toLocaleDateString(locale as any) : ""}
strings={DayPickerStrings}
allowTextInput={true}
onSelectDate={(date) => {
  if (date) props.valueChanged(date.toISOString());
  else props.valueChanged('');
}}
/>);

  ;
}

function GetDate(props){
  const value = (props.value && moment(props.value).isValid()) ? moment(props.value,"YYYY-MM-DD").format('L') : '';
  return <FieldDateRenderer text={value} />;
}

export default class HelloWorld extends React.Component<IHelloWorldProps,IBookLibCollection,React.FC> {

  private _selection: Selection;
  //firstDayOfWeek = React.useState(DayOfWeek.Sunday);

  
  
  private _onItemsSelectionChanged = () => {
    
    
    this.setState({
      BookListItem: (this._selection.getSelection()[0] as BookLibListItem)
    });
  }

  private _getListItems(): Promise<BookLibListItem[]> {
    const url: string = this.props.siteUrl + "/_api/web/lists/getbytitle('Book%20Library')/items";
    return this.props.context.spHttpClient.get(url,SPHttpClient.configurations.v1)
    .then(response => {
      console.log(response);
    return response.json();
    })
    .then(json => {
    return json.value;
    }) as Promise<BookLibListItem[]>;
    }
    public bindDetailsList(message: string) : void {

      this._getListItems().then(listItems => {
        console.log(listItems);
        this.setState({ BookListItems: listItems,status: message});
      });
    }
  
    public componentDidMount(): void {
      this.bindDetailsList("All Records have been loaded Successfully");  
  
      
    }

    @autobind
  public btnAdd_click(): void {  

    const url: string = this.props.siteUrl + "/_api/web/lists/getbytitle('Book%20Library')/items";          
       
       const spHttpClientOptions: ISPHttpClientOptions = {
      "body": JSON.stringify(this.state.BookListItem)
    };

    this.props.context.spHttpClient.post(url,SPHttpClient.configurations.v1,spHttpClientOptions)
    .then((response: SPHttpClientResponse) => {
     
      if (response.status === 201) {
        this.bindDetailsList("Record added and All Records were loaded Successfully");         

       
       
      } else {
        let errormessage: string = "An error has occured i.e.  " + response.status + " - " + response.statusText;
        this.setState({status: errormessage});        
      }
    });
  }

   @autobind
  public btnUpdate_click(): void {

    let id: number = this.state.BookListItem.Id;

    const url: string = this.props.siteUrl + "/_api/web/lists/getbytitle('Book%20Library')/items(" + id + ")";          
      
    
    const headers: any = {
      "X-HTTP-Method": "MERGE","IF-MATCH": "*",};
       
       const spHttpClientOptions: ISPHttpClientOptions = {
        "headers": headers,"body": JSON.stringify(this.state.BookListItem)
    };

    this.props.context.spHttpClient.post(url,spHttpClientOptions)
    .then((response: SPHttpClientResponse) => {
     
      if (response.status === 204) {
        this.bindDetailsList("Record Updated and All Records were loaded Successfully");                
       
      } else {
        let errormessage: string = "An error has occured i.e.  " + response.status + " - " + response.statusText;
        this.setState({status: errormessage});        
      }
    });
  }

  @autobind
  public btnDelete_click(): void {
    let id: number = this.state.BookListItem.Id;

    

    const url: string = this.props.siteUrl + "/_api/web/lists/getbytitle('Book%20Library')/items(" + id + ")";          

    
    const headers: any = { "X-HTTP-Method": "DELETE","IF-MATCH": "*" };

    const spHttpClientOptions: ISPHttpClientOptions = {
      "headers": headers
    };


    this.props.context.spHttpClient.post(url,spHttpClientOptions)
    .then((response: SPHttpClientResponse) => {
      if (response.status === 204) {
        alert("record got deleted successfully....");
        this.bindDetailsList("Record deleted and All Records were loaded Successfully");   
        
      } else {
        let errormessage: string = "An error has occured i.e.  " + response.status + " - " + response.statusText;
        this.setState({status: errormessage}); 
      }
    });
  }

  constructor(props: IHelloWorldProps,state: IBookLibCollection){
    super(props);

    this.state = {

      status: 'Ready',BookListItems: [],BookListItem:{
        Id: 0,Title: "",Author: "",Publisher: "",DateOfPublish: new Date,ISBN: 0
      },};

    this._selection = new Selection({
      onSelectionChanged: this._onItemsSelectionChanged,});

    
  }



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

    const dropdownRef = React.createRef<IDropdown>();

    return (

      <div className={ styles.helloWorld }>
     
      <TextField                  
      label="ID"
      required={ false } 
      value={ (this.state.BookListItem.Id).toString()}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.Id=e;}}
    />
    <TextField                  
      label="Title"
      required={ true } 
      value={ (this.state.BookListItem.Title)}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.Title=e;}}
    />
    <TextField                  
      label="Author"
      required={ true } 
      value={ (this.state.BookListItem.Author)}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.Author=e;}}
    />
    <TextField                  
      label="Publisher"
      required={ true } 
      value={ (this.state.BookListItem.Publisher)}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.Publisher=e;}}
    />
   <SetDate 
       value={this.state.BookListItem.DateOfPublish} 
       required={true}
       Title="DateOfPublish"
       //firstDayOfWeek={firstDayOfWeek} 
    /> 
    
    <TextField                  
      label="ISBN"
      required={ true } 
      value={ (this.state.BookListItem.ISBN.toString())}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.ISBN=e;}}
    />
    

<p className={styles.title}>
         <PrimaryButton
          text='Add'      
          title='Add'              
          onClick={this.btnAdd_click}
        />

        <PrimaryButton
          text='Update'                    
          onClick={this.btnUpdate_click}
        />

        <PrimaryButton
          text='Delete'                    
          onClick={this.btnDelete_click}
        />
        
      </p>


    <div id="divstatus">
      {this.state.status}
    </div>

    <div>
    <DetailsList
          items={ this.state.BookListItems}
          columns={ _bookListColumns }
          setKey='Id'
          checkBoxVisibility={ CheckBoxVisibility.onHover}
          selectionMode={ SelectionMode.single}
          layoutMode={ DetailsListLayoutMode.fixedColumns }
          compact={ true }
          selection={this._selection}                                         
      />
      </div>  


</div>


    );
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

我使用以下界面作为 SharePoint 中列表的数据模型:

export interface BookLibListItem {
Id: number;
Title: string;
Author: string;
Publisher: string;
DateOfPublish: Date;
ISBN: number;

}

感谢有关此问题的任何建议。

解决方法

Sharepoint 中的日期可能很棘手(尤其是在多个本地化的情况下) - 因此我使用 moment 来格式化日期。对于 DatePicker,我正在使用此代码(带有矩库):

<DatePicker
{...props.value && moment(props.value).isValid() ? { value: moment(props.value).toDate() } : {}}
className={css(styles.dateFormField,'dateFormField')}
placeholder={strings.FormFields.DateFormFieldPlaceholder}
isRequired={props.fieldSchema.Required}
ariaLabel={props.fieldSchema.Title}
parseDateFromString={(dateStr?: string) => { return moment(dateStr,'L').toDate(); }}
formatDate={(date: Date) => (typeof date.toLocaleDateString === 'function') ? date.toLocaleDateString(locale) : ''}
strings={strings.FormFields}
firstDayOfWeek={props.fieldSchema.FirstDayOfWeek}
allowTextInput={true}
onSelectDate={(date) => {
  if (date) props.valueChanged(date.toISOString());
  else props.valueChanged('');
}}

上面的代码是呈现日期选择器的单独组件。要保存日期,它应该是 ISO 格式(函数 date.toISOString())。

要显示日期,我正在使用 FieldDateRenderer - import { FieldDateRenderer } from "@pnp/spfx-controls-react/lib/FieldDateRenderer";

const value = (props.value && moment(props.value).isValid()) ? moment(props.value,"YYYY-MM-DD").format('L') : '';
return <FieldDateRenderer text={value} />;
,

您如何阅读和写入列表?您是在编写自己的请求,还是在使用 @pnp/sp (pnpjs) 之类的库?如果您一开始不使用 pnpjs,我会推荐它,请检查 Getting Started Guide

Fluent UI / office-ui-fabric-react <DatePicker/>Date 对象作为值,并在值更改时输出相同的值。当您从 SharePoint 获取日期时,它可能会表示为 ISO 字符串,您可以使用以下命令将其解析为 Date 对象:

const date = new Date(columnValueFromSharePoint);

为此,您不需要像 moment 这样的额外库。如果您需要进行某种复杂的时区调整或日期操作,我只会在这种情况下推荐 moment。当您需要做一些简单的事情时,例如添加几分钟、几小时、几天或几个月,您可以使用标准的原始 Date 方法非常简单地完成这些操作。

每当您需要将 Date 写回 SharePoint 时,您都可以将日期输出为 ISO 字符串:

const result = await sp.web.lists.getByTitle('mylist').items.add({
   Title: 'New Item',MyDateColumn: date.toISOString()
});

Fluent UI(尚)不包含日期和时间选择器,但 @pnp/spfx-controls-react 项目包含。与 DatePicker 一样,它采用标准 Date 对象作为值并在其 onChange 事件中输出相同的值。这意味着当您需要填充 SharePoint 列时,您可以轻松地将其转换为 ISO 字符串。

有关在 JavaScript 中操作 Date 对象的参考通常可在 MDN Web Docs 上获得