BlockItem.jsx源码
/root/workspace/actionview/av-github-source-code/actionview-fe/app/components/gantt/BlockItem.jsx

import React, { PropTypes, Component } from 'react';
import { OverlayTrigger, Popover, Grid, Row, Col, ControlLabel } from 'react-bootstrap';
import _ from 'lodash';
const moment = require('moment');
export default class BlockItem extends Component {
  constructor(props) {
    super(props);
  }
  static propTypes = {
    cellWidth: PropTypes.number.isRequired,
    blockHeight: PropTypes.number.isRequired,
    origin: PropTypes.number.isRequired,
    foldIssues: PropTypes.array.isRequired,
    issue: PropTypes.object.isRequired,
    options: PropTypes.object.isRequired,
    mode: PropTypes.string.isRequired
  }
  //shouldComponentUpdate(newProps, newState) {
  //  if (newProps.cellWidth != this.props.cellWidth
  //    || newProps.origin != this.props.origin
  //    || newProps.mode != this.props.mode
  //    || this.props.foldIssues.indexOf(this.props.issue.id) !== -1
  //    || newProps.foldIssues.indexOf(this.props.issue.id) !== -1
  //    || this.props.issue.id == newProps.selectedIssue.id 
  //    || this.props.issue.id == newProps.selectedIssue.parent_id) {
  //    return true;
  //  }
  //  return false;
  //}
  render() {
    const { 
      cellWidth,
      blockHeight,
      origin, 
      foldIssues, 
      issue,
      options:{ states=[] },
      mode
    } = this.props;
    const stateColors = { new : '#ccc', inprogress: '#3db9d3', completed: '#3c9445' };
    const popover=(
      <Popover id='popover-trigger-hover' style={ { maxWidth: '350px', padding: '15px 0px' } }>
        <Grid>
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>主题</Col>
            <Col sm={ 8 }><div style={ { textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap' } }>{ issue.no + '-' + issue.title }</div></Col>
          </Row>
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>开始时间</Col>
            <Col sm={ 8 }>{ issue.expect_start_time ? moment.unix(issue.expect_start_time).format('YYYY/MM/DD') : <span style={ { fontStyle: 'italic', color: '#aaa' } }>未指定</span> }</Col>
          </Row>
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>结束时间</Col>
            <Col sm={ 8 }>{ issue.expect_complete_time ? moment.unix(issue.expect_complete_time).format('YYYY/MM/DD') : <span style={ { fontStyle: 'italic', color: '#aaa' } }>未指定</span> }</Col>
          </Row>
          { mode == 'progress' &&
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>进度</Col>
            <Col sm={ 8 }>{ issue.progress ? issue.progress + '%' : '0%' }</Col>
          </Row> }
          { mode == 'status' &&
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>状态</Col>
            <Col sm={ 8 }>{ _.findIndex(states, { id: issue.state }) != -1 ? <span className={ 'state-' + _.find(states, { id: issue.state }).category + '-label' }>{ _.find(states, { id: issue.state }).name }</span>: '-' }</Col>
          </Row> }
        </Grid>
      </Popover>);
    const start = moment.unix(issue.expect_start_time || issue.expect_complete_time || issue.created_at).startOf('day').format('X');
    const end = moment.unix(issue.expect_complete_time || issue.expect_start_time || issue.created_at).startOf('day').format('X');
    const size = (end - start) / 3600 / 24 + 1;
    const offset = (start - origin) / 3600 / 24;
    const width = size * cellWidth - 3;
    let backgroundColor = '#ccc';
    if (mode == 'progress') {
      if ((!issue.expect_start_time || !issue.expect_complete_time) && (!issue.progress || issue.progress < 100)) {
        backgroundColor = '#555';
      } else {
        backgroundColor = issue.hasSubtasks ? '#65c16f' : '#3db9d3';
      }
    } else if (mode == 'status') {
      const stateInd = _.findIndex(states, { id: issue.state });
      if (stateInd !== -1) {
        const category = states[stateInd].category;
        if ((!issue.expect_start_time || !issue.expect_complete_time) && category !== 'completed') {
          backgroundColor = '#555';
        } else {
          backgroundColor = stateColors[category];
        }
      }
    }
    const progressBGColor = issue.hasSubtasks ? '#3c9445' : '#2898b0';
    return (
      <div className='ganttview-block-container'>
        <OverlayTrigger trigger={ [ 'hover', 'focus' ] } rootClose placement='top' overlay={ popover }>
          { issue.hasSubtasks && foldIssues.indexOf(issue.id) === -1 ?
          <div className='ganttview-block-parent'
            id={ issue.id + '-block' }
            data-id={ issue.id }
            style={ { width: width + 'px', marginLeft: (offset * cellWidth + 1) + 'px' } }>
            <div className='ganttview-block-parent-left'/>
            <div className='ganttview-block-parent-right'/>
          </div>
          :
          <div
            className={ 'ganttview-block ' + (issue.hasSubtasks ? '' : 'ganttview-block-movable') }
            id={ issue.id + '-block' }
            data-id={ issue.id }
            style={ { width: width + 'px', height: blockHeight + 'px', marginLeft: (offset * cellWidth + 1) + 'px', backgroundColor } }>
            { mode == 'progress' &&
            <div
              className='ganttview-block-progress'
              style={ { height: blockHeight + 'px', width: (width * _.min([ _.max([ issue.progress || 0, 0 ]), 100 ]) / 100) + 'px', backgroundColor: progressBGColor } }/> }
          </div> }
        </OverlayTrigger>
      </div> ); 
  }
}源码解读
这段代码定义了一个名为 BlockItem 的 React 组件,用于渲染甘特图中的一个任务块(或称为区块)。这个组件负责根据传入的属性动态生成任务块,并根据不同的模式(进度或状态)显示不同的样式和信息。
代码分析
导入模块
import React, { PropTypes, Component } from 'react';
import { OverlayTrigger, Popover, Grid, Row, Col, ControlLabel } from 'react-bootstrap';
import _ from 'lodash';
const moment = require('moment');- 
React:React 的核心库。 - 
PropTypes:用于定义组件的 prop 类型检查。 - 
react-bootstrap:提供了 Bootstrap 风格的 React 组件,这里使用了OverlayTrigger,Popover,Grid,Row,Col,ControlLabel。 - 
lodash:一个实用工具库,提供了很多常用的函数,如_.findIndex和_.find。 - 
moment:一个流行的 JavaScript 时间日期处理库。 
定义组件类
export default class BlockItem extends Component {
  constructor(props) {
    super(props);
  }- 
BlockItem类继承自Component,表示这是一个 React 组件。 - 
constructor构造函数初始化组件,并调用父类构造函数super(props)来初始化父类属性。 
定义 prop 类型
static propTypes = {
    cellWidth: PropTypes.number.isRequired,
    blockHeight: PropTypes.number.isRequired,
    origin: PropTypes.number.isRequired,
    foldIssues: PropTypes.array.isRequired,
    issue: PropTypes.object.isRequired,
    options: PropTypes.object.isRequired,
    mode: PropTypes.string.isRequired
  }propTypes是一个静态属性,用于定义组件接受的 prop 类型。
- 
cellWidth:每个单元格的宽度。 - 
blockHeight:任务块的高度。 - 
origin:时间起点的 Unix 时间戳。 - 
foldIssues:一个数组,包含需要折叠的任务 ID。 - 
issue:一个对象,包含任务的信息。 - 
options:一个对象,包含配置项。 - 
mode:模式,可以是'progress'或'status'。 
shouldComponentUpdate 方法(注释掉)
//shouldComponentUpdate(newProps, newState) {
  //  if (newProps.cellWidth != this.props.cellWidth
  //    || newProps.origin != this.props.origin
  //    || newProps.mode != this.props.mode
  //    || this.props.foldIssues.indexOf(this.props.issue.id) !== -1
  //    || newProps.foldIssues.indexOf(this.props.issue.id) !== -1
  //    || this.props.issue.id == newProps.selectedIssue.id 
  //    || this.props.issue.id == newProps.selectedIssue.parent_id) {
  //    return true;
  //  }
  //  return false;
  //}- 这是一个生命周期方法,用于判断组件是否需要重新渲染。
 
- 如果某些关键属性发生变化,则返回 
true表示需要更新。 - 如果没有变化,则返回 
false表示不需要更新。 - 注意:此方法已被注释掉,意味着默认情况下组件将遵循 React 的默认更新策略。
 
渲染方法
render() {
    const { 
      cellWidth,
      blockHeight,
      origin, 
      foldIssues, 
      issue,
      options:{ states=[] },
      mode
    } = this.props;
    const stateColors = { new : '#ccc', inprogress: '#3db9d3', completed: '#3c9445' };
    const popover=(
      <Popover id='popover-trigger-hover' style={ { maxWidth: '350px', padding: '15px 0px' } }>
        <Grid>
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>主题</Col>
            <Col sm={ 8 }><div style={ { textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap' } }>{ issue.no + '-' + issue.title }</div></Col>
          </Row>
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>开始时间</Col>
            <Col sm={ 8 }>{ issue.expect_start_time ? moment.unix(issue.expect_start_time).format('YYYY/MM/DD') : <span style={ { fontStyle: 'italic', color: '#aaa' } }>未指定</span> }</Col>
          </Row>
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>结束时间</Col>
            <Col sm={ 8 }>{ issue.expect_complete_time ? moment.unix(issue.expect_complete_time).format('YYYY/MM/DD') : <span style={ { fontStyle: 'italic', color: '#aaa' } }>未指定</span> }</Col>
          </Row>
          { mode == 'progress' &&
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>进度</Col>
            <Col sm={ 8 }>{ issue.progress ? issue.progress + '%' : '0%' }</Col>
          </Row> }
          { mode == 'status' &&
          <Row>
            <Col sm={ 4 } componentClass={ ControlLabel } style={ { textAlign: 'right' } }>状态</Col>
            <Col sm={ 8 }>{ _.findIndex(states, { id: issue.state }) != -1 ? <span className={ 'state-' + _.find(states, { id: issue.state }).category + '-label' }>{ _.find(states, { id: issue.state }).name }</span>: '-' }</Col>
          </Row> }
        </Grid>
      </Popover>);
    const start = moment.unix(issue.expect_start_time || issue.expect_complete_time || issue.created_at).startOf('day').format('X');
    const end = moment.unix(issue.expect_complete_time || issue.expect_start_time || issue.created_at).startOf('day').format('X');
    const size = (end - start) / 3600 / 24 + 1;
    const offset = (start - origin) / 3600 / 24;
    const width = size * cellWidth - 3;
    let backgroundColor = '#ccc';
    if (mode == 'progress') {
      if ((!issue.expect_start_time || !issue.expect_complete_time) && (!issue.progress || issue.progress < 100)) {
        backgroundColor = '#555';
      } else {
        backgroundColor = issue.hasSubtasks ? '#65c16f' : '#3db9d3';
      }
    } else if (mode == 'status') {
      const stateInd = _.findIndex(states, { id: issue.state });
      if (stateInd !== -1) {
        const category = states[stateInd].category;
        if ((!issue.expect_start_time || !issue.expect_complete_time) && category !== 'completed') {
          backgroundColor = '#555';
        } else {
          backgroundColor = stateColors[category];
        }
      }
    }
    const progressBGColor = issue.hasSubtasks ? '#3c9445' : '#2898b0';
    return (
      <div className='ganttview-block-container'>
        <OverlayTrigger trigger={ [ 'hover', 'focus' ] } rootClose placement='top' overlay={ popover }>
          { issue.hasSubtasks && foldIssues.indexOf(issue.id) === -1 ?
          <div className='ganttview-block-parent'
            id={ issue.id + '-block' }
            data-id={ issue.id }
            style={ { width: width + 'px', marginLeft: (offset * cellWidth + 1) + 'px' } }>
            <div className='ganttview-block-parent-left'/>
            <div className='ganttview-block-parent-right'/>
          </div>
          :
          <div
            className={ 'ganttview-block ' + (issue.hasSubtasks ? '' : 'ganttview-block-movable') }
            id={ issue.id + '-block' }
            data-id={ issue.id }
            style={ { width: width + 'px', height: blockHeight + 'px', marginLeft: (offset * cellWidth + 1) + 'px', backgroundColor } }>
            { mode == 'progress' &&
            <div
              className='ganttview-block-progress'
              style={ { height: blockHeight + 'px', width: (width * _.min([ _.max([ issue.progress || 0, 0 ]), 100 ]) / 100) + 'px', backgroundColor: progressBGColor } }/> }
          </div> }
        </OverlayTrigger>
      </div> ); 
  }
}render方法返回组件的 JSX 结构。
- 解构赋值 
const { ... } = this.props来提取传入的 props。 - 定义状态颜色 
stateColors。 - 创建一个 
Popover用于显示任务的详细信息。 - 使用 
moment计算任务的开始时间和结束时间,并转换为 Unix 时间戳。 - 计算任务块的大小 
size和偏移量offset。 - 计算任务块的宽度 
width。 - 根据模式 
mode设置背景颜色backgroundColor。 - 根据模式 
mode设置进度背景颜色progressBGColor。 - 渲染任务块:
 
- 如果任务有子任务并且不在折叠列表中,则渲染一个带有左右箭头的父任务块。
 - 否则,渲染一个普通任务块,并根据模式显示进度条。
 - 使用 
OverlayTrigger使得当鼠标悬停或聚焦时显示Popover。 
总结
这个 BlockItem 组件负责渲染甘特图中的一个任务块,并根据不同的模式(进度或状态)显示不同的样式和信息。组件还提供了一个 Popover 弹出框,用于展示任务的详细信息。通过这种方式,组件能够灵活地适应不同的数据和布局需求,为用户提供清晰的任务进度或状态视图,并且提供了交互性,增强了用户体验。
                









