[File-X Demo] — 事件管理

[File-X Demo] — 事件管理

上一个教程,我们的应用已经可以以“当前用户”的身份来调用数据。那么,其实我们的应用已经可以“当前用户”的身份来,创建、发布、删除的“事件”的能力了。

用户的 Token 其实是有使用范围的限制的,只是现在我们的应用已经获得的相关的授权,所以这里先不要考虑中。更多 OAuth 2.0 Scope 的介绍可以查看: OAuth Scopes

这一部分教程,我们改变一下编写代码的步骤。不再以页面的角度来一步一步进行开发,而是从代码层次的角度展开工作。

准备请求方法

首先,我们会根据 File-X Event 的 API 文档 ,将所有需要调用的接口编写成一个个可供调用的函数。

打开 src/requests.js ,开始添加方法

// create event
export const createEvent = async (data, token) => {
  const url = `${API_ENDPOINT}/api/xfile/event/`
  return await fetch(url, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/json',
      Authorization: `${token.token_type} ${token.access_token}`
    }
  }).then(resHelper)
}

// update event
export const updateEvent = async (id, data, token) => {
  const url = `${API_ENDPOINT}/api/xfile/event/${id}/`
  return await fetch(url, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/json',
      Authorization: `${token.token_type} ${token.access_token}`
    },
    body: JSON.stringify(data)
  }).then(resHelper)
}

// publish event
export const publishEvent = async (id, token) => {
  return updateEvent(id, { publish: true }, token)
}

// delete event
export const deleteEvent = async (id, token) => {
  const url = `${API_ENDPOINT}/api/xfile/event/${id}/`
  return await fetch(url, {
    method: 'DELETE',
    headers: {
      Authorization: `${token.token_type} ${token.access_token}`,
      Accept: 'application/json'
    }
  }).then(resHelper)
}

添加“创建事件”的功能

在刚开始的交互流程中,我们可以看到,用户可以通过点击“我的”页面上的“+”按钮,进入“创建事件“页面

添加“新建事件”功能

接下来,我们添加一个 NewButton 组件。新建 src/NewButton.js,编辑:

import React from 'react'
import './NewButton.css'

function NewButton (props) {
  return <button className='NewButton' onClick={props.onClick} />
}

export default NewButton

添加样式文件 src/NewButton.css,并编辑:

.NewButton {
  position: fixed;
  z-index: 10;
  bottom: 64px;
  right: 24px;
  background-color: #333;
  width: 48px;
  height: 48px;
  border: none;
  border-radius: 24px;
  box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.3);
  transition: 0.3s ease all;
}
.NewButton:active {
  transform: scale(0.95);
}

.NewButton::before,
.NewButton::after {
  content: '';
  position: absolute;
  top: calc(50% - 1.5px);
  left: calc(50% - 13px);
  width: 26px;
  height: 3px;
  background-color: white;
}

.NewButton::after {
  transform: rotate(90deg);
}

在“我的”页面中,添加按钮。打开 src/Me.js 并编辑

import NewButton from './NewButton'

function Me (props) {
  // ...codes
  ;<div className='App__content'>
    <EventList request={handleListRequest} showUserInfo={false} />
    {/* 添加 NewButton 代码 */}
    <NewButton
      onClick={() => {
        props.history.push('/event/new')
      }}
    />
  </div>
}

打开浏览器,访问“我的”页面,看到的效果如下图所示:

点击按钮,可以进入“创建事件”页面

实现“创建事件”页面

“创建事件”页面其实就是一个表单, 我们可以引入地方的库(如 Formik),来管理表单的状态。但是在这个教程里面,只是简单的 react 组件来进行表单设计。

在开始表单编辑之前,我们先继续文章开头的思路,将数据先接口,再去处理交互。所以我们先来修改一下 src/EventCreation.js 使其能够创建一个事件。

打开src/EventCreation.js,并编辑:

import React, { useContext } from 'react'
import { createEvent, publishEvent } from './requests'
import { AuthInfo } from './AuthInfoProvider'
function EventCreation () {
  const {
    state: { token }
  } = useContext(AuthInfo)
  return (
    <div>
      <button
        onClick={() => {
          const date = new Date()
          const formatted = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`
          const payload = {
            title: 'Demo',
            content: 'DEMO CONTENT',
            html_content: '<h1>Demo</h1><p>DEMO CONTENT</p>',
            event_time: formatted
          }
          createEvent(payload, token)
            .then(({ data }) => {
              console.log(data)
            })
            .catch(err => {
              global.alert(err.message)
            })
        }}
      >
        Demo 事件
      </button>
    </div>
  )
}

export default EventCreation

我们在 EventCreation 页面添加了一个按钮,点击这个按钮后,应用会以“当前用户”的身份创建一个“事件”。当创建成功后,会在 console 中输出刚刚新建的“事件”。

接下来,我们开始进行“事件创建表单”的编写。 可以先把上面添加的带面注释掉。

import React, { useContext, useState } from 'react'
import { createEvent, publishEvent } from './requests'
import { AuthInfo } from './AuthInfoProvider'

const getCurrentDate = () => {
  const date = new Date()
  return `${date.getFullYear()}-${date.toLocaleDateString('en-US', {
    month: '2-digit'
  })}-${date.getDate()}`
}
function EventCreation (props) {
  const {
    state: { token }
  } = useContext(AuthInfo)
  const [formValues, setFormValues] = useState({
    title: '',
    content: '',
    event_time: getCurrentDate()
  })
  const [isSubmitting, setIsSubmitting] = useState(false)
  return (
    <form
      className='App'
      onSubmit={e => {
        e.preventDefault()
        // TODO should validate data before submit.
        setIsSubmitting(true)
        createEvent(
          {
            ...formValues,
            html_content: `<h1>${formValues.title}</h1><p>${formValues.content}</p>`
          },
          token
        )
          .then(({ data }) => {
            console.log(data)
            return publishEvent(data.id, token)
            // 返回上一页
          })
          .then(() => {
            props.history.goBack()
          })
          .catch(err => {
            global.alert(err.message)
            setIsSubmitting(false)
          })
      }}
    >
      <div className='App__header'>
        <div className='PageHeader'>
          <div className='PageHeader__left pl_12'>
            <button
              className='button button_merge'
              type='button'
              onClick={() => {
                props.history.goBack()
              }}
              disabled={isSubmitting}
            >
              取消
            </button>
          </div>
          <h3 className='PageTitle'>新事件</h3>
          <div className='PageHeader__right pr_12'>
            <button
              className='button button_merge'
              type='submit'
              disabled={isSubmitting}
            >
              发布
            </button>
          </div>
        </div>
      </div>
      <div className='App__content Form'>
        <div className='FormItem'>
          <label htmlFor='event_time' className='FormLabel'>
            日期
          </label>
          <input
            type='date'
            id='event_time'
            name='event_time'
            className='FormInput'
            value={formValues.event_time}
            onChange={e => {
              setFormValues({
                ...formValues,
                event_time: e.target.value
              })
            }}
          />
        </div>
        <div className='FormItem'>
          <label htmlFor='title' className='FormLabel'>
            标题
          </label>
          <input
            type='text'
            id='title'
            name='title'
            value={formValues.title}
            className='FormInput'
            onChange={e => {
              setFormValues({
                ...formValues,
                title: e.target.value
              })
            }}
          />
        </div>
        <div className='FormItem'>
          <label htmlFor='content' className='FormLabel'>
            内容
          </label>
          <textarea
            id='content'
            value={formValues.content}
            name='content'
            className='FormInput'
            onChange={e => {
              setFormValues({
                ...formValues,
                content: e.target.value
              })
            }}
          />
        </div>
      </div>
    </form>
  )
}

export default EventCreation

打开 src/index.css,添加表单相关样式

.Form {
  padding-left: 12px;
}
.FormItem {
  padding: 5px 12px 5px 3px;
  border-bottom: 1px dashed #ccc;
}
.FormLabel {
  font-size: 14px;
  display: block;
  margin-bottom: 5px;
}
.FormInput {
  display: block;
  width: 100%;
  border: none;
  background: #f3f3f3;
  font-size: 16px;
  padding: 3px;
  resize: vertical;
  box-sizing: border-box;
}

textarea.FormInput {
  min-height: 3em;
}

编辑完成后,访问“我的”页面,看到的效果如下图所示:

输入内容后,点击“发布”按钮,我们就可以完成“事件”的创建,及发布。

注意:上面的代码中,我们在创建“事件”之后,立刻调用了“发布”接口,所以达到了点击“发布”按钮就可以发布事件的效果,跳过了原模型设计中的“草稿”功能。

添加 “删除事件” 功能

接下来我们要实现删除事件的功能,我们将在 EventItem 里面添加一个“删除”按钮。当“当前用户”为“事件”拥有者时,用户可以看到”删除“按钮。用户点击”删除”按钮后,页面弹出提示框要求用户进行确认。待用户确认删除“事件”后,应用向后台提交“删除”请求。 待后台返回删除结构后,更新页面列表数据。

打开 src/EventItem.js,开始修改

import React, { useContext } from 'react'
import { AuthInfo } from './AuthInfoProvider'
import { deleteEvent } from './requests'

function EventItem (props) {
  const {
    state: { token, currentUser }
  } = useContext(AuthInfo)
  const canDelete = currentUser && currentUser.uid === data.user.uid
  return (
    <div className='EventItem'>
      {/* ... */}
      {canDelete && (
        <button
          className='EventItem__deleteBtn button button_dashed button_sm'
          onClick={() => {
            if (window.confirm('确定删除事件?')) {
              deleteEvent(data.id, token)
                .then(() => {
                  props.onDeleted(data)
                })
                .catch(err => {
                  alert(err.message)
                })
            }
          }}
        >
          ×
        </button>
      )}
    </div>
  )
}

上面的代码中,我们使用从 AuthInfoContext 中获取到了当前用户数据,并根据情况显示删除按钮。并且在调用 File-X.Event: delete 的API来删除事件,删除成功之后,我们需要通知上层的组件 EventList,将 “事件”从列表中删除。

打开 src/EventList.js,并编辑

// 找到 EventItem
<EventItem 
    data={item} 
    showUserInfo={showUserInfo}
    key={item.id} 
    onDeleted={(data) => {
        const filtered = state.items.filter((item) => item.id !== data.id);
        setState({
            ...state, 
            items: filtered
        })
    }}
/>

编辑后的效果如下图:

小结

这一章节中,我们实现了两个功能点:1. 新建并发布事件 2. 删除事件。包含的知识点包含:

  • 使用 React 制作简单的网页表单,并将数据提交到服务器上。
  • 如何通过 props 的方式,将“子组件”的变化通知到“父组件”

额外任务

更新事件的功能,还没有实现,大家可以参考教程中提到的思路来尝试一下。

  1. 判断当前用户是否有权修改“事件”。如果“有”,显示修改按钮。用户点击后进入“事件编辑”页面
  2. 添加“事件编辑”路由
  3. 参考“事件创建”,新建表单,根据“事件”的数据,初始化表单
  4. 待“事件”更新后,通知“父组件”更新列表数据。

发表评论

您的电子邮箱地址不会被公开。

您可以使用以下 HTML标签和属性:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Captcha Code