text:
<repo_name>lance-li/platon-static<file_sep>/src/modules/forum/variable.less
// 颜色
@color_white: #FFF;
@color_theme: #5BCACA;
@color_strong: #55CBCB;
@color_weak: #CCC;
@color_weak_background: #F5F5F5;
@color_context: #333;
@color_title: #666;
@color_help: #999;
@color_orange: #f87048;
// 字体
@font_title: 1.7em;
@font_context: 1.6em;
@font_click: 1.3em;
@font_index: 1.2em;
@font_help: 1.1em;
// 圆角
@border-radius: 4px;
// 按钮
@button_circle_height: 45px;
@button_footer_height: 49px;
<file_sep>/src/modules/home/components/activity/ActivityHome.less
.activity-home-component {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  height: 10rem;
  margin: 2rem 0 4rem;
  .name {
    position: absolute;
    top: 0;
    font-size: 1.7rem;
    line-height: 1.7rem;
    color: #333333;
  }
  .holder {
    position: absolute;
    top: 3.5rem;
    font-size: 1.3rem;
    color: #333333;
    text-align: left;
  }
  .holding-time {
    position: absolute;
    top: 6rem;
    font-size: 1.2rem;
    color: #666666;
    text-align: left;
  }
  .location {
    position: absolute;
    bottom: 0;
    font-size: 1.2rem;
    color: #999999;
    text-align: left;
  }
  .status {
    position: absolute;
    font-size: 1.2rem;
    line-height: 1.7rem;
    text-align: center;
    top: 0;
    right: 11rem;
    &.prepare {
      color: #ff983f;
    }
    &.closed {
      color: #666666;
    }
    &.review {
      color: #4fd3cd;
    }
  }
  .thumbnail {
    position: absolute;
    right: 0;
    top: 0;
    width: 10rem;
    height: 10rem;
    border-radius: 4px;
  }
}
<file_sep>/src/modules/plan/PlanList.tsx
import * as React from 'react'
import './PlanList.less'
import { connect } from 'react-redux'
import { startLoad, endLoad, alertMsg } from '../../redux/actions'
import { loadPlanList, createCampPlan, openAudition, unlockCampPlan } from './async'
import AssetImg from '../../components/AssetImg'
import { changeTitle } from '../../utils/helpers'
import { ToolBar } from '../base/ToolBar'
import Swiper from 'swiper'
import Banner from '../../components/Banner'
import { Dialog } from 'react-weui'
import * as _ from 'lodash'
import { MarkBlock } from '../../components/markblock/MarkBlock'
import { mark } from 'utils/request'

const {Alert} = Dialog

/**
 * rise_icon_hr 左侧较宽 TODO
 */
@connect(state => state)
export default class PlanList extends React.Component<any, any> {
  constructor () {
    super()
    this.state = {
      riseMember: 1,
      showPage: false,
      auditions: []
    }
    this.picWidth = (window.innerWidth - 15 - 10 - 10) / 2.5
    this.picHeight = (80 / 130) * this.picWidth
    this.runPicWidth = (window.innerWidth / (750 / 270)) > 135 ? 135 : (window.innerWidth / (750 / 270))
    this.runTextWidth = (window.innerWidth - 30 - this.runPicWidth - 5)
    this.completedRightTextWidth = 45
    let gup = 0
    if (window.innerWidth > 350) {
      gup = 30
    }
    this.completedLeftTextWidth = window.innerWidth - 56 - 84 - 8 - gup - 15 - 45
    changeTitle('圈外同学')
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  componentWillMount () {
    mark({
      module: '打点',
      function: '学习',
      action: '打开学习页面',
      memo: window.ENV.osName
    })

    const {dispatch, location} = this.props
    dispatch(startLoad())

    loadPlanList().then(res => {
      dispatch(endLoad())
      if (res.code === 200) {
        const {runningPlans = [], riseMember, recommendations = [], currentCampPlans = [], auditions = []} = res.msg

        let showEmptyPage = false
        if (!runningPlans || runningPlans.length === 0) {
          showEmptyPage = true
        }
        this.setState({
          planList: res.msg,
          showEmptyPage: showEmptyPage,
          riseMember,
          currentCampPlans: currentCampPlans,
          recommendations: recommendations,
          auditions: auditions,
          showPage: true,
          campBanner: res.msg.campBanner
        }, () => {
          var swiper = new Swiper('#problem-recommendation', {
            scrollbar: '#problem-recommendation-bar',
            scrollbarHide: true,
            slidesPerView: 'auto',
            centeredSlides: false,
            spaceBetween: 0,
            grabCursor: true
          })
        })
      } else {
        dispatch(alertMsg(res.msg))
      }
    }).catch(ex => {
      dispatch(endLoad())
      dispatch(alertMsg(ex))
    })
  }

  handleClickPlan (plan) {
    const {learnable, startDate} = plan
    const {dispatch} = this.props
    if (learnable) {
      this.context.router.push({
        pathname: '/rise/static/plan/study',
        query: {planId: plan.planId}
      })
    } else {
      dispatch(alertMsg(`专项课将于${startDate}统一开营\n在当天开始学习哦！`))
    }
  }

  handleClickAuditionPlan (plan) {
    const {planId, errMsg} = plan
    const {dispatch} = this.props
    // 如果 planId 为 null，代表当前课程未开，点击弹窗提醒
    if (errMsg) {
      dispatch(alertMsg(errMsg))
      return
    }
    if (planId) {
      this.context.router.push(`/rise/static/plan/study?planId=${planId}`)
    } else {
      this.setState({
        dialogButtons: [
          {
            label: '取消',
            onClick: () => {
              this.setState({dialogShow: false, dialogButtons: [], dialogContent: ''})
            }
          },
          {
            label: '确认',
            onClick: () => {
              dispatch(startLoad())
              this.setState({dialogShow: false})
              openAudition().then(res => {
                dispatch(endLoad())
                if (res.code === 200) {
                  this.context.router.push(`/rise/static/plan/study?planId=${res.msg}`)
                } else {
                  dispatch(alertMsg(res.msg))
                }
              }).catch(e => {
                dispatch(alertMsg(e))
              })
            }
          }
        ],
        dialogShow: true,
        dialogContent: '试听课开启后，学习期限为30天。期间完成学习即可永久查看内容。确认开启吗？'
      })
    }
  }

  handleClickCampPlan (planId, problemId) {
    const {dispatch} = this.props
    if (planId === null) {
      // 如果 planId 为 null，代表当前课程未开，点击弹窗提醒
      this.setState({
        dialogButtons: [
          {
            label: '取消',
            onClick: () => {
              this.setState({dialogShow: false, dialogButtons: [], dialogContent: ''})
            }
          },
          {
            label: '确认',
            onClick: () => {
              dispatch(startLoad())
              this.setState({dialogShow: false})
              createCampPlan(problemId).then(res => {
                dispatch(endLoad())
                if (res.code === 200) {
                  this.context.router.push(`/rise/static/plan/study?planId=${res.msg}`)
                } else {
                  dispatch(alertMsg(res.msg))
                }
              }).catch(e => {
                dispatch(alertMsg(e))
              })
            }
          }
        ],
        dialogShow: true,
        dialogContent: '课程开启后，学习期限为30天。期间完成学习即可永久查看内容。确认开启吗？'
      })
    } else {
      // 如果 planId 不为 null，则当前课程正在学习当中，点击进入学习页面开始学习
      unlockCampPlan(planId).then(res => {
        if (res.code === 200) {
          this.context.router.push(`/rise/static/plan/study?planId=${planId}`)
        } else {
          dispatch(alertMsg(res.msg))
        }
      }).catch(e => {
        dispatch(alertMsg(e))
      })
    }
  }

  handleClickProblemChoose () {
    this.context.router.push({
      pathname: '/rise/static/problem/explore'
    })
  }

  handleClickRecommend (problem) {
    this.context.router.push({pathname: '/rise/static/plan/view', query: {id: problem.id}})
  }

  handleClickMoreProblem () {
    this.context.router.push({
      pathname: '/rise/static/problem/explore'
    })
  }

  handleClickBanner (banner) {
    let url = banner.linkUrl
    if (url.indexOf('https') >= 0) {
      window.location.href = url
    } else {
      this.context.router.push(url)
    }
  }

  render () {
    const {planList = {}, showEmptyPage, riseMember, showPage, currentCampPlans = [], recommendations = [], auditions} = this.state
    const {location} = this.props
    const {completedPlans, runningPlans = []} = planList
    const renderAuditions = () => {
      // if(riseMember !== 1) return
      if (_.isEmpty(auditions)) return
      return (

            试听课

          {
            auditions.map((item, index) => {
              return (
                <MarkBlock module={'打点'} func={'学习'} action={'点击试听课'} className="problem-block" key={index}
                           onClick={() => {
                             this.handleClickAuditionPlan(item)
                           }}>

                      <div className={`problem-item-backcolor catalog${item.problem.catalogId}`}/>
                      <div className={`problem-item-backimg catalog${item.problem.catalogId}`}/>

{item.problem.abbreviation}

                        {item.name}

                      {
                        item.planId ?

                          已完成：{`${item.completeSeries}/${item.totalSeries}节`}

 : null
                      }
                      {item.planId ? renderDeadline(item.deadline) : null}
                      {
                        item.planId ? <div className={`running-problem-button`}>去上课
 :
                          <div className={`running-problem-button`}>{item.errMsg ? '即将开课' : '开课'}

                      }

                </MarkBlock>
              )
            })
          }

      )

    }
    const renderCampProblems = () => {
      if (riseMember !== 1) return
      return (

            本月专项课

          {
            currentCampPlans.map((item, index) => {
              return (
                <MarkBlock module={'打点'} func={'学习'} action={'点击训练营课程'} memo={window.ENV.osName}
                           className="problem-block" key={index}
                           onClick={() => {
                             this.handleClickCampPlan(item.planId, item.problemId)
                           }}>

                      <div className={`problem-item-backcolor catalog${item.problem.catalogId}`}/>
                      <div className={`problem-item-backimg catalog${item.problem.catalogId}`}/>

{item.problem.abbreviation}

                        {item.name}

                      {
                        item.planId ?

                          已完成：{`${item.completeSeries}/${item.totalSeries}节`}

 : null
                      }
                      {renderDeadline(item.deadline)}
                      {
                        item.planId ? <div className={`running-problem-button`}>去上课
 :
                          <div className={`running-problem-button`}>开课

                      }

                </MarkBlock>
              )
            })
          }

      )
    }

    const renderDeadline = (deadline) => {
      if (!deadline || deadline < 0) {
        return null
      } else {
        return (

            距关闭：{deadline} 天

        )
      }
    }

    const renderBanners = () => {
      const banners = [{
        'imageUrl': 'https://static.iqycamp.com/images/fragment/rise_member_banner_2.jpg?imageslim',
        'linkUrl': `https://${window.location.hostname}/pay/rise`
      }, {
        'imageUrl': this.state.campBanner,
        'linkUrl': `https://${window.location.hostname}/pay/camp`
      }]
      if (banners.length === 0 || riseMember === 1) return
      return (
        <Banner>
          {
            banners.map((banner, index) => {
              return (
                <MarkBlock module={'打点'} func={'学习'} action={'点击学习首页banner'} memo={banner.linkUrl}
                           className="banner-item swiper-slide" key={index}
                           onClick={() => this.handleClickBanner(banner)}>

                </MarkBlock>
              )
            })
          }
        </Banner>
      )
    }

    const renderDialog = () => {
      const {dialogShow = false, dialogButtons = [], dialogContent} = this.state

      return (
        <Alert
          show={dialogShow}
          buttons={dialogButtons}
        >
          {dialogContent}
        </Alert>
      )
    }

    return (

        {renderBanners()}
        <ToolBar/>
        {renderAuditions()}
        {renderCampProblems()}

            进行中

          {showEmptyPage ?

              <AssetImg url="https://static.iqycamp.com/images/plan_empty.png" width={55} height={56}/>

              还没有学习中的课程哦

            <MarkBlock module={'打点'} func={'学习'} action={'点击选课'} memo={window.ENV.osName}
                       className="plp-empty-button">去选课
            </MarkBlock>

 : runningPlans.map((item, key) => {
            return (
              <div id={`problem-${item.planId}`}
                   className={`problem-block`}
                   key={key} onClick={() => this.handleClickPlan(item)}>

                    <div className={`problem-item-backcolor catalog${item.problem.catalogId}`}/>
                    <div className={`problem-item-backimg catalog${item.problem.catalogId}`}/>

{item.problem.abbreviation}

                      {item.name}

                      已完成：{`${item.completeSeries}/${item.totalSeries}节`}

                    {renderDeadline(item.deadline)}
                    <div className={`running-problem-button ${item.learnable ? '' : 'lock'}`}>
                      {item.learnable ? '去上课' : '即将开营'}

            )
          })}

        {recommendations && recommendations.length !== 0 ?

            推荐学习

              {recommendations ? recommendations.map((problem, index) => {
                return (
                  <MarkBlock module={'打点'} func={'学习'} action={'学习页面点击推荐课程'} memo={window.ENV.osName}
                             key={index} className="problem-item-show swiper-slide"
                             onClick={() => this.handleClickRecommend(problem)}>

                      {problem.newProblem ?
                        <AssetImg url="https://static.iqycamp.com/images/fragment/problem_new_icon_03.png"
                                  style={{zIndex: 1, left: 0, top: 0}} size={25}/> : null
                      }
                      {problem.trial ?
                        <AssetImg url="https://static.iqycamp.com/images/fragment/problem_trial_icon_01.png"
                                  style={{zIndex: 1, left: 6, top: 6}} width={20}/> : null
                      }
                      <div className={`problem-item-backcolor catalog${problem.catalogId}`}/>
                      <div className={`problem-item-backimg catalog${problem.catalogId}`}/>

{problem.abbreviation}

                    {problem.problem}
                  </MarkBlock>
                )
              }) : null}
              <MarkBlock module={'打点'} func={'学习'} action={'学习页面点击发现更多'} memo={window.ENV.osName}
                         onClick={() => this.handleClickMoreProblem()}
                         className="swiper-slide problem-item-show found-more">

                  点击发现更多

                  <AssetImg type="rise_icon_arrow_right" size="25px"/>

              </MarkBlock>

 : null}

            已完成

          {completedPlans && completedPlans.length !== 0 ?

              {completedPlans.map((plan, key) => {
                return (
                  <div id={`problem-${plan.planId}`} className={`p-c-c-r-block ${key === 0 ? 'first' : ''}`}
                       onClick={() => this.handleClickPlan(plan)}>

                          {plan.name}

                          {plan.closeTime}

                        {plan.point}分

                )
              })}

 : null}
          {completedPlans && completedPlans.length === 0 ?

            <AssetImg url='https://static.iqycamp.com/images/complete_plan_empty.png?imageslim'/>

 : null}

        {renderDialog()}

    )
  }
}
<file_sep>/src/components/Sidebar.tsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { merge } from 'lodash'
import './Sidebar.less'

const CANCEL_DISTANCE_ON_SCROLL = 20

const defaultStyles = {
  root: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    overflow: 'hidden'
  },
    sidebar: {
    zIndex: 600,
    position: 'absolute',
    top: 0,
    bottom: 0,
    transition: 'transform .3s ease-out',
    WebkitTransition: '-webkit-transform .3s linear',
    willChange: 'transform',
    overflow: 'visible',
    backgroundColor: '#76C8DF',
    width: '70%'
  },
  content: {
    position: 'absolute',
    zIndex: 500,
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    overflowY: 'scroll',
    WebkitOverflowScrolling: 'touch',
    transition: 'left .3s linear, right .3s linear'
  },
  overlay: {
    zIndex: 550,
    position: 'fixed',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    opacity: 0,
    visibility: 'hidden',
    transition: 'opacity .3s linear, visibility .3s linear',
    // backgroundColor: 'rgba(0,0,0,.3)'
  },
  dragHandle: {
    zIndex: 100,
    position: 'fixed',
    top: 0,
    bottom: 0
  }
}

export class Sidebar extends Component {
  constructor(props) {
    super(props)

    this.state = {
      // the detected width of the sidebar in pixels
      sidebarWidth: props.defaultSidebarWidth,

      // keep track of touching params
      touchIdentifier: null,
      touchStartX: null,
      touchStartY: null,
      touchCurrentX: null,
      touchCurrentY: null,

      // if touch is supported by the browser
      dragSupported: false
    }

    this.overlayClicked = this.overlayClicked.bind(this)
    this.onTouchStart = this.onTouchStart.bind(this)
    this.onTouchMove = this.onTouchMove.bind(this)
    this.onTouchEnd = this.onTouchEnd.bind(this)
    this.onScroll = this.onScroll.bind(this)
    this.saveSidebarRef = this.saveSidebarRef.bind(this)
  }

  componentDidMount() {
    this.setState({
      dragSupported: typeof window === 'object' && 'ontouchstart' in window,
      ctlOpen: true
    })
    this.saveSidebarWidth()
  }

  componentDidUpdate() {
    // filter out the updates when we're touching
    if(!this.isTouching()) {
      this.saveSidebarWidth()
    }
  }

  onTouchStart(ev) {
    // filter out if a user starts swiping with a second finger
    if(!this.isTouching()) {
      const touch = ev.targetTouches[ 0 ]
      this.setState({
        touchIdentifier: touch.identifier,
        touchStartX: touch.clientX,
        touchStartY: touch.clientY,
        touchCurrentX: touch.clientX,
        touchCurrentY: touch.clientY
      })
    }
  }

  onTouchMove(ev) {
    if(this.isTouching()) {
      for(let ind = 0; ind < ev.targetTouches.length; ind++) {
        // we only care about the finger that we are tracking
        if(ev.targetTouches[ ind ].identifier === this.state.touchIdentifier) {
          this.setState({
            touchCurrentX: ev.targetTouches[ ind ].clientX,
            touchCurrentY: ev.targetTouches[ ind ].clientY
          })
          break
        }
      }
    }
  }

  onTouchEnd() {
    if(this.isTouching()) {
      // trigger a change to open if sidebar has been dragged beyond dragToggleDistance
      const touchWidth = this.touchSidebarWidth()

      if(this.props.open && touchWidth < this.state.sidebarWidth - this.props.dragToggleDistance ||
        !this.props.open && touchWidth > this.props.dragToggleDistance) {
        this.props.onSetOpen(!this.props.open)
      }

      this.setState({
        touchIdentifier: null,
        touchStartX: null,
        touchStartY: null,
        touchCurrentX: null,
        touchCurrentY: null
      })
    }
  }

  // This logic helps us prevents the user from sliding the sidebar horizontally
  // while scrolling the sidebar vertically. When a scroll event comes in, we're
  // cancelling the ongoing gesture if it did not move horizontally much.
  onScroll() {
    if(this.isTouching() && this.inCancelDistanceOnScroll()) {
      this.setState({
        touchIdentifier: null,
        touchStartX: null,
        touchStartY: null,
        touchCurrentX: null,
        touchCurrentY: null
      })
    }
  }

  // True if the on going gesture X distance is less than the cancel distance
  inCancelDistanceOnScroll() {
    let cancelDistanceOnScroll

    if(this.props.pullRight) {
      cancelDistanceOnScroll = Math.abs(this.state.touchCurrentX - this.state.touchStartX) <
        CANCEL_DISTANCE_ON_SCROLL
    } else {
      cancelDistanceOnScroll = Math.abs(this.state.touchStartX - this.state.touchCurrentX) <
        CANCEL_DISTANCE_ON_SCROLL
    }
    return cancelDistanceOnScroll
  }

  isTouching() {
    return this.state.touchIdentifier !== null
  }

  overlayClicked() {
    if(this.props.open) {
      this.props.onSetOpen(false)
    }
  }

  saveSidebarWidth() {
    const width = this.sidebar.offsetWidth

    if(width !== this.state.sidebarWidth) {
      this.setState({ sidebarWidth: width })
    }
  }

  saveSidebarRef(node) {
    this.sidebar = node
  }

  // calculate the sidebarWidth based on current touch info
  touchSidebarWidth() {
    // if the sidebar is open and start point of drag is inside the sidebar
    // we will only drag the distance they moved their finger
    // otherwise we will move the sidebar to be below the finger.
    if(this.props.pullRight) {
      if(this.props.open && window.innerWidth - this.state.touchStartX < this.state.sidebarWidth) {
        if(this.state.touchCurrentX > this.state.touchStartX) {
          return this.state.sidebarWidth + this.state.touchStartX - this.state.touchCurrentX
        }
        return this.state.sidebarWidth
      }
      return Math.min(window.innerWidth - this.state.touchCurrentX, this.state.sidebarWidth)
    }

    if(this.props.open && this.state.touchStartX < this.state.sidebarWidth) {
      if(this.state.touchCurrentX > this.state.touchStartX) {
        return this.state.sidebarWidth
      }
      return this.state.sidebarWidth - this.state.touchStartX + this.state.touchCurrentX
    }
    return Math.min(this.state.touchCurrentX, this.state.sidebarWidth)
  }

  render() {
    const sidebarStyle = merge({}, defaultStyles.sidebar, this.props.styles.sidebar)
    const contentStyle = merge({}, defaultStyles.content, this.props.styles.content)
    const overlayStyle = merge({}, defaultStyles.overlay, this.props.styles.overlay)
    const useTouch = this.state.dragSupported && this.props.touch
    const isTouching = this.isTouching()
    const rootProps = {
      className: this.props.rootClassName,
      style: merge({}, defaultStyles.root, this.props.styles.root),
      role: 'navigation'
    }
    let dragHandle

    // sidebarStyle right/left
    if(this.props.pullRight) {
      sidebarStyle.right = 0
      sidebarStyle.transform = 'translateX(100%)'
      sidebarStyle.WebkitTransform = 'translateX(100%)'
      if(this.props.shadow) {
        sidebarStyle.boxShadow = '-2px 2px 4px rgba(0, 0, 0, 0.15)'
      }
    } else {
      sidebarStyle.left = 0
      sidebarStyle.transform = 'translateX(-100%)'
      sidebarStyle.WebkitTransform = 'translateX(-100%)'
      if(this.props.shadow) {
        sidebarStyle.boxShadow = '2px 2px 4px rgba(0, 0, 0, 0.15)'
      }
    }

    if(isTouching) {
      const percentage = this.touchSidebarWidth() / this.state.sidebarWidth

      // slide open to what we dragged
      if(this.props.pullRight) {
        sidebarStyle.transform = `translateX(${(1 - percentage) * 100}%)`
        sidebarStyle.WebkitTransform = `translateX(${(1 - percentage) * 100}%)`
      } else {
        sidebarStyle.transform = `translateX(-${(1 - percentage) * 100}%)`
        sidebarStyle.WebkitTransform = `translateX(-${(1 - percentage) * 100}%)`
      }

      // fade overlay to match distance of drag
      overlayStyle.opacity = percentage
      overlayStyle.visibility = 'visible'
    } else if(this.props.docked) {
      // show sidebar
      if(this.state.sidebarWidth !== 0) {
        sidebarStyle.transform = `translateX(0%)`
        sidebarStyle.WebkitTransform = `translateX(0%)`
      }

      // make space on the left/right side of the content for the sidebar
      if(this.props.pullRight) {
        contentStyle.right = `${this.state.sidebarWidth}px`
      } else {
        contentStyle.left = `${this.state.sidebarWidth}px`
      }
    } else if(this.props.open) {
      // slide open sidebar
      sidebarStyle.transform = `translateX(0%)`
      sidebarStyle.WebkitTransform = `translateX(0%)`

      // show overlay
      overlayStyle.opacity = 1
      overlayStyle.visibility = 'visible'
    }

    if(isTouching || !this.props.transitions) {
      sidebarStyle.transition = 'none'
      sidebarStyle.WebkitTransition = 'none'
      contentStyle.transition = 'none'
      overlayStyle.transition = 'none'
    }

    if(useTouch) {
      if(this.props.open) {
        rootProps.onTouchStart = this.onTouchStart
        rootProps.onTouchMove = this.onTouchMove
        rootProps.onTouchEnd = this.onTouchEnd
        rootProps.onTouchCancel = this.onTouchEnd
        rootProps.onScroll = this.onScroll
      } else {
        const dragHandleStyle = merge({}, defaultStyles.dragHandle, this.props.styles.dragHandle)
        dragHandleStyle.width = this.props.touchHandleWidth

        // dragHandleStyle right/left
        if(this.props.pullRight) {
          dragHandleStyle.right = 0
        } else {
          dragHandleStyle.left = 0
        }

        dragHandle = (

        )
      }
    }
    return (

          {this.props.sidebar}
          {/*<div className={`side-bar-controller ${this.state.ctlOpen ? 'open' : ''}`}*/}
               {/*onClick={() => {*/}
                 {/*this.props.trigger()*/}
               {/*}}>*/}
            {/*{this.props.open ? '收起' : '提纲'}*/}
          {/*
*/}

          {dragHandle}
          {this.props.children}

    )
  }
}

Sidebar.propTypes = {
  // main content to render
  children: PropTypes.node.isRequired,

  // styles
  styles: PropTypes.shape({
    root: PropTypes.object,
    sidebar: PropTypes.object,
    content: PropTypes.object,
    overlay: PropTypes.object,
    dragHandle: PropTypes.object
  }),

  // root component optional class
  rootClassName: PropTypes.string,

  // sidebar optional class
  sidebarClassName: PropTypes.string,

  // content optional class
  contentClassName: PropTypes.string,

  // content optional id
  contentId: PropTypes.string,

  // overlay optional class
  overlayClassName: PropTypes.string,

  // sidebar content to render
  sidebar: PropTypes.node.isRequired,

  // boolean if sidebar should be docked
  docked: PropTypes.bool,

  // boolean if sidebar should slide open
  open: PropTypes.bool,

  // boolean if transitions should be disabled
  transitions: PropTypes.bool,

  // boolean if touch gestures are enabled
  touch: PropTypes.bool,

  // max distance from the edge we can start touching
  touchHandleWidth: PropTypes.number,

  // Place the sidebar on the right
  pullRight: PropTypes.bool,

  // Enable/Disable sidebar shadow
  shadow: PropTypes.bool,

  // distance we have to drag the sidebar to toggle open state
  dragToggleDistance: PropTypes.number,

  // callback called when the overlay is clicked
  onSetOpen: PropTypes.func,

  // Intial sidebar width when page loads
  defaultSidebarWidth: PropTypes.number
}

Sidebar.defaultProps = {
  docked: false,
  open: false,
  transitions: true,
  touch: true,
  touchHandleWidth: 20,
  pullRight: false,
  shadow: true,
  dragToggleDistance: 30,
  onSetOpen: () => {},
  styles: {},
  defaultSidebarWidth: 0
}
<file_sep>/src/modules/practice/knowledge/KnowledgeViewer.tsx
import * as React from 'react'
import { connect } from 'react-redux'
import './KnowledgeViewer.less'
import AssetImg from '../../../components/AssetImg'
import Audio from '../../../components/Audio'
import WordUnfold from '../../../components/WordUnfold'
import {
  loadDiscuss,
  discussKnowledge,
  loadKnowledge,
  loadKnowledges,
  deleteKnowledgeDiscuss,
  learnKnowledge, loadKnowledgePriorityDiscuss,
} from './async'
import _ from 'lodash'
import { startLoad, endLoad, alertMsg, set } from '../../../redux/actions'
import { scroll } from '../../../utils/helpers'
import { mark } from '../../../utils/request'
import QYVideo from '../../../components/QYVideo'
import { FooterButton } from '../../../components/submitbutton/FooterButton'
import { Block } from '../../../components/Block'
import { SectionProgressHeader } from '../components/SectionProgressHeader'
import { ColumnSpan } from '../../../components/ColumnSpan'
import KnowledgeDiscussDistrict from './KnowledgeDiscussDistrict/KnowledgeDiscussDistrict'

const sequenceMap = {
  0: 'A',
  1: 'B',
  2: 'C',
  3: 'D',
  4: 'E',
  5: 'F',
  6: 'G',
}

@connect(state => state)
export default class KnowledgeViewer extends React.Component<any, any> {
  constructor () {
    super()
    this.state = {
      showTip: false,
      showDiscuss: false,
      commentId: 0,
      knowledge: {},
      discuss: {},
      placeholder: '提出你的疑问或意见吧（限1000字）',
      isReply: false,
      content: '',
    }
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired,
  }

  async componentWillMount () {
    mark({ module: '打点', function: '学习', action: '打开知识点页面' })
    const { dispatch, location } = this.props
    const { id, practicePlanId, complete } = this.props.location.query

    if (practicePlanId) {
      let knowledge = await loadKnowledges(practicePlanId)
      this.setState({ knowledge: knowledge.msg[0], referenceId: knowledge.msg[0].id })
      let discussRes = await loadKnowledgePriorityDiscuss(knowledge.msg[0].id)
      this.setState({ discussData: discussRes.msg })

      if (complete == 'false') {
        // 完成练习动效
        dispatch(set('completePracticePlanId', practicePlanId))
      }
    } else if (id) {
      let knowledge = await loadKnowledge(id)
      this.setState({ knowledge: knowledge.msg })
      let discussRes = await loadKnowledgePriorityDiscuss(id)
      this.setState({ discussData: discussRes.msg })
    }
  }

  reply (item) {
    this.setState({
      showDiscuss: true,
      isReply: true,
      placeholder: '回复 ' + item.name + ':',
      content: '',
      repliedId: item.id,
      referenceId: item.knowledgeId,
    })
  }

  reload () {
    const { knowledge } = this.state
    loadDiscuss(knowledge.id, 1).then(res => {
      if (res.code === 200) {
        this.setState({
          discuss: res.msg, showDiscuss: false, repliedId: 0, isReply: false,
          placeholder: '提出你的疑问或意见吧（限1000字）', content: '',
        })
        scroll('.discuss', '.container')
      }
    })
  }

  onChange (value) {
    this.setState({ content: value })
  }

  cancel () {
    this.setState({ placeholder: '提出你的疑问或意见吧（限1000字）', isReply: false, showDiscuss: false, repliedId: 0 })
  }

  onSubmit () {
    mark({
      module: '打点',
      function: '学习',
      action: '点击提交知识点评论',
    })
    const { dispatch } = this.props
    const { referenceId, repliedId, content } = this.state
    if (content.length == 0) {
      dispatch(alertMsg('请填写评论'))
      return
    }

    let discussBody = { comment: content, referenceId: this.state.knowledge.id }

    if (repliedId) {
      _.merge(discussBody, { repliedId: repliedId })
    }

    discussKnowledge(discussBody).then(res => {
      const { code, msg } = res
      if (code === 200) {
        this.reload()
      }
      else {
        dispatch(alertMsg(msg))
      }
    }).catch(ex => {
      dispatch(alertMsg(ex))
    })
  }

  onDelete (id) {
    const { dispatch } = this.props
    const { knowledge } = this.state
    dispatch(startLoad())
    deleteKnowledgeDiscuss(id).then(res => {
      loadDiscuss(knowledge.id, 1).then(res => {
        dispatch(endLoad())
        if (res.code === 200) {
          this.setState({
            discuss: res.msg, showDiscuss: false, repliedId: 0, isReply: false,
            placeholder: '提出你的疑问或意见吧（限1000字）', content: '',
          })
        }
      })
    }).catch(ex => {
      dispatch(endLoad())
      dispatch(alertMsg(ex))
    })
  }

  handleClickGoWarmup (practicePlanId) {
    const { dispatch } = this.props
    dispatch(startLoad())
    mark({ module: '打点', function: '知识点', action: '完成知识点学习' })
    learnKnowledge(practicePlanId).then(res => {
      dispatch(endLoad())
      if (res.code === 200) {
        this.refs.sectionProgress.goNextPage()
      } else {
        dispatch(alertMsg(res.msg))
      }
    }).catch(er => alertMsg(er))
  }

  render () {
    const { showTip, showDiscuss, knowledge, discuss = [], isReply, placeholder } = this.state
    const {
      analysis, means, keynote, audio, audioWords, pic, example, analysisPic, meansPic, keynotePic,
      analysisAudio, analysisAudioWords, meansAudio, meansAudioWords,description,keynoteAudio, keynoteAudioWords, videoUrl, videoPoster, videoWords,
    } = knowledge
    const { location } = this.props
    const { practicePlanId, planId, complete } = location.query

    const choiceRender = (choice, idx) => {
      const { id, subject } = choice
      return (
        <div key={id}
             className={`choice${choice.isRight ? ' right' : ''}`}>
          <span className={`index`}>
            {sequenceMap[idx]}

          <span className={`subject`}>{subject}

      )
    }

    const rightAnswerRender = (choice, idx) => {
      return (choice.isRight ? sequenceMap[idx] + ' ' : '')
    }

    return (
      <Block>
        <div className={`knowledge-view-container`}>
          {
            practicePlanId ?
              <SectionProgressHeader ref={'sectionProgress'}
                                     practicePlanId={practicePlanId}
                                     planId={planId}/> :

{knowledge.knowledge}

          }
          {
            videoUrl &&

            <QYVideo videoUrl={videoUrl}
                                 videoPoster={videoPoster}
                     videoWords={videoWords}/>

          }

            {
              audio &&

                <Audio url={audio}
                       words={audioWords}/>

            }

            {
              description &&

            }

            {pic &&

}
            {
              analysis &&

                  <AssetImg height={17}
                            url="https://static.iqycamp.com/images/fragment/analysis3.png"/>

                {analysisAudio &&

<Audio url={analysisAudio}
                                                      words={analysisAudioWords}/>
}

                {analysisPic &&

}

            }
            {
              means &&

                  <AssetImg height={17}
                            url="https://static.iqycamp.com/images/fragment/means3.png"/>

                {meansAudio &&
<Audio url={meansAudio}
                                                                     words={meansAudioWords}/>
}

                {meansPic &&

}

            }
            {
              keynote &&

                  <AssetImg height={17}
                            url="https://static.iqycamp.com/images/fragment/keynote3.png"/>

                {
                  keynoteAudio &&

<Audio url={keynoteAudio}
                                                        words={keynoteAudioWords}/>

                }

                {keynotePic &&

}

            }
            {
              example &&

                  <AssetImg height={17}
                            url="https://static.iqycamp.com/images/fragment/example2.png"/>

                  {example.choiceList.map((choice, idx) => choiceRender(choice, idx))}

                {
                  showTip ?

解析

                        正确答案：{example.choiceList.map((choice, idx) => rightAnswerRender(choice, idx))}

 :
                    <WordUnfold words="点击查看解析"
                                onUnfold={() => this.setState({ showTip: true })}/>
                }

            }
            <ColumnSpan height={15}
                        style={{ margin: '2rem -2.5rem 0' }}/>
            <KnowledgeDiscussDistrict data={this.state.discussData}
                                      clickFunc={() => {
                                        this.context.router.push({
                                          pathname: '/rise/static/practice/submit/comment',
                                          query: {
                                            referenceId: this.state.knowledge.id,
                                            type: 1,
                                          },
                                        })
                                      }}/>

          {
            practicePlanId &&
            <FooterButton btnArray={[
              {
                click: () => this.handleClickGoWarmup(practicePlanId),
                text: complete == 'true' ? '下一题' : '学完了，下一题',
              }]}/>
          }

      </Block>
    )
  }
}
<file_sep>/src/modules/forum/question/AnswerComment.tsx
import * as React from "react";
import { connect } from "react-redux"
import "./AnswerComment.less"
import { DialogHead, DialogBottomIcon } from "../commons/ForumComponent"
import { approveAnswer, commentAnswer, commentAnswerDel, disApproveAnswer, getAnswer } from "../async";
import { mark } from "../../../utils/request"
import Discuss from "../../practice/components/Discuss";
import { splitText, removeHtmlTags, changeTitle } from "../../../utils/helpers";
import { startLoad, endLoad, alertMsg } from "../../../redux/actions";
import { Dialog } from "react-weui";
import PullSlideTip from '../../../components/PullSlideTip'
const { Alert } = Dialog;

interface AnswerCommentState {
  answerInfo: object;
  // 评论数
  commentCount: number;
  // 评论列表
  commentlist: object;
  // 添加、回复评论
  answerId: number;
  comment: string;
  repliedCommentId: number;
  // 删除评论
  commentId: number;
  // 输入框提示
  placeholder: string;
  // 是否展示输入框
  showDiscussBox: boolean;
  // 待预览图片
  previewImgs: object;
}
const initDiscussBoxState = {
  comment: '',
  repliedCommentId: null,
  commentId: null,
  placeholder: '',
  showDiscussBox: false
}
@connect(state => state)
export default class AnswerComment extends React.Component<any, AnswerCommentState> {

  constructor() {
    super()
    this.state = {
      answerInfo: {},
      commentlist: null,
      answerId: null,
      comment: '',
      repliedCommentId: null,
      commentId: null,
      placeholder: '',
      showDiscussBox: false,
      end: true,
      show: false,
      previewImgs: []
    }
  }

  componentWillMount() {
    changeTitle('论坛')
    mark({module: "打点", function: "论坛", action: "打开回答评论页"});
    let answerId =  this.props.answerId
    if(!answerId) {
      answerId = this.props.location.query.answerId
    }
    const { dispatch } = this.props
    this.setState({ answerId })
    dispatch(startLoad())
    getAnswer(answerId).then(res => {
      dispatch(endLoad())
      const { code, msg } = res
      if(code === 200) {
        this.setState({
          answerInfo: res.msg, commentlist: res.msg.comments, commentCount: res.msg.comments.length
        }, () => {
          // 设置图片预览对象
          this.setState({ previewImgs: document.getElementsByTagName('img') })
        })
      } else {
        dispatch(alertMsg(msg))
      }
    }).catch(e => {
      dispatch(endLoad())
      dispatch(alertMsg(e))
    })
  }

  componentDidMount() {

  }

  componentDidUpdate() {
    // 添加图片预览功能
    const { previewImgs } = this.state
    for(let i = 0; i < previewImgs.length; i++) {
      if(previewImgs[i].src.indexOf("answer-") > 0) {
        let imgSrc = previewImgs[i].src
        previewImgs[i].onclick = () => {
          wx.previewImage({ current: imgSrc, urls: [imgSrc] });
        }
      }
    }
  }

  // 提交答案
  commentAnswer() {
    const { commentCount, commentlist, answerId, comment, repliedCommentId } = this.state
    const { dispatch } = this.props
    dispatch(startLoad())
    commentAnswer(answerId, comment, repliedCommentId).then(res => {
      dispatch(endLoad())
      const { code, msg } = res
      if(code === 200) {
        commentlist.push(msg)
        this.setState({ commentlist, commentCount: commentCount + 1 }, () => {
          this.handleClickHideDiscussBox()
          if(this.refs[`commentRef${msg.id}`]) {
            document.querySelector("#react-app").scrollTop = this.refs[`commentRef${msg.id}`].offsetTop
          }
        })
      } else {
        dispatch(alertMsg(msg))
      }
    }).catch(e => {
      dispatch(endLoad())
      dispatch(alertMsg(e))
    })
  }

  commentAnswerDel() {
    const { dispatch } = this.props;
    const { commentCount, commentId } = this.state;
    dispatch(startLoad())
    commentAnswerDel(commentId).then(res => {
      dispatch(endLoad());
      this.setState({ show: false, commentCount: commentCount - 1 });
      const { code, msg } = res;
      if(code === 200) {
        let removeNode = `commentRef${commentId}`;
        this.refs[removeNode].style.display = "None";
      } else {
        dispatch(alertMsg(msg))
      }
    }).catch(e => {
      dispatch(endLoad())
      dispatch(alertMsg(e))
    })
  }

  // 直接回复答案本身
  handleClickCommentAnswer(answerId) {
    this.setState(initDiscussBoxState, () => {
      this.setState({
        placeholder: '和答主互动一下吧~',
        showDiscussBox: true
      })
    })
  }

  // 回复答案的某一条评论
  handleClickCommentReply(answerId, repliedCommentId, authorUserName) {
    this.setState(initDiscussBoxState, () => {
      this.setState({
        repliedCommentId: repliedCommentId,
        placeholder: `回复 ${authorUserName}：`,
        showDiscussBox: true
      })
    })
  }

  // 隐藏输入框，并初始化 state 中的值
  handleClickHideDiscussBox() {
    this.setState(initDiscussBoxState)
  }

  // 编辑器内部内容映射到当前组件
  handleOnchangeDiscussBoxValue(value) {
    this.setState({
      comment: value
    })
  }

  // 初始化删除状态
  handleDelete(commentId) {
    this.setState({ commentId, show: true })
  }

  render() {
    const alertProps = {
      buttons: [
        { label: '再想想', onClick: () => this.setState({ show: false }) },
        { label: '确定', onClick: () => this.commentAnswerDel() }
      ],
    }

    const {
      answerInfo, commentlist, commentCount, comment, placeholder, showDiscussBox, show
    } = this.state;
    const {
      answer, approval, approvalCount, authorHeadPic, authorUserName, comments,
      id, mine, publishTimeStr, questionId, topic
    } = answerInfo;
    const renderAnswer = () => {
      if(!answer) return
      let tag = approval
      let comment = 'https://static.iqycamp.com/images/fragment/comment.png?imageslim'
      let unvote = 'https://static.iqycamp.com/images/fragment/unvote.png?imageslim'
      let voted = 'https://static.iqycamp.com/images/fragment/voted.png?imageslim'
      let btn2ImgUrl = approval ? voted : unvote
      const changeBtn2ImgUrl = () => {
        if(tag) {
          // 已赞同，则取消赞同
          disApproveAnswer(id)
        } else {
          // 还未赞同，点击赞同
          approveAnswer(id)
        }
        tag = !tag
        return tag ? voted : unvote
      }
      let isExpand = false
      const expandComment = () => {
        let node = this.refs.ansContent
        if(isExpand) {
          node.innerHTML = splitText(answer, 68)
        } else {
          node.innerHTML = answer
        }
        isExpand = !isExpand
        return isExpand ? "收起" : "展开"
      }
      return (

          <DialogHead leftImgUrl={authorHeadPic} user={authorUserName} time={publishTimeStr}/>

          <DialogBottomIcon
            leftContent={removeHtmlTags(answer).length > 68 ? '展开' : false} leftContentFunc={expandComment}
            btn1ImgUrl={comment} btn1Content={commentCount}
            btn1ContentFunc={this.handleClickCommentAnswer.bind(this, this.state.answerId)}
            btn2ImgUrl={btn2ImgUrl} btn2Content={approvalCount} btn2ContentFunc={changeBtn2ImgUrl}
          />

      )
    }

    const renderAnswerComments = () => {
      if(!commentlist) return

      return (

          {
            commentlist.map((commentItem, idx) => {
              const {
                answerId, authorHeadPic, authorUserName, comment, id, mine,
                publishTimeStr, repliedComment, repliedName
              } = commentItem

              const renderRepliedComment = () => {
                if(repliedName && repliedComment) {
                  return (

                      回复{repliedName}：{repliedComment}

                  )
                }
              }
              return (
                <div className="ans-comment-desc" ref={`commentRef${commentItem.id}`}>
                  <DialogHead leftImgUrl={authorHeadPic} user={authorUserName} time={publishTimeStr}
                              rightContent={mine ? `删除` : `回复`}
                              rightContentFunc={
                                mine ?
                                  this.handleDelete.bind(this, commentItem.id) :
                                  this.handleClickCommentReply.bind(this, this.state.answerId, commentItem.id, commentItem.authorUserName)
                              }/>

{comment}

                  {renderRepliedComment()}

              )
            })
          }
          <PullSlideTip isEnd={this.state.end}/>
          <Alert { ...alertProps }
                 show={show}>
            <div className="global-pre" dangerouslySetInnerHTML={{ __html: `确认要删除评论吗？` }}/>
          </Alert>

      )
    }

    const renderDiscussBox = () => {
      if(showDiscussBox) {
        return (
          <Discuss
            placeholder={placeholder} onChange={(v) => this.handleOnchangeDiscussBoxValue(v)}
            submit={this.commentAnswer.bind(this)} limit={1000}
            cancel={this.handleClickHideDiscussBox.bind(this)}/>
        )
      }
    }

    return (

          {renderAnswer()}
          {renderAnswerComments()}

        {renderDiscussBox()}

    )
  }

}
<file_sep>/src/components/Tutorial.less
.tutorial-mask {
  background-color: transparent;
  position: absolute;
  z-index: 2000;
  width: 100%;
  height: 100%;
  .tutorial {
    width: 100%;
    height: 100%;
    .item {
      background-color: rgba(0, 0, 0, 0.75);
      img {
        width: 100%;
        display: block;
      }
      width: 100%;
    }
  }
}

<file_sep>/src/modules/forum/async.ts
import { pget, ppost } from "utils/request";

// 首页获取所有问题详情
export function getAllQuestions(page) {
  if(!page) page = 1
  return pget(`/forum/question/load/list`, { page })
}

// 关注问题
export function follow(questionId) {
  return ppost(`/forum/question/follow/${questionId}`)
}

// 取消关注
export function disFollow(questionId) {
  return ppost(`/forum/question/follow/cancel/${questionId}`)
}

// 根据 questionId 获取问题信息
export function getQuestion(questionId) {
  return pget(`/forum/question/load/${questionId}`)
}

// 对答案进行赞同
export function approveAnswer(answerId) {
  return ppost(`/forum/answer/approve/${answerId}`)
}

// 对答案取消赞同
export function disApproveAnswer(answerId) {
  return ppost(`/forum/answer/approve/cancel/${answerId}`)
}

// 根据 answerId 获取答案详情
export function getAnswer(answerId) {
  return pget(`/forum/answer/load/${answerId}`)
}

// 提交答案
// 新增答案，则第三个参数不用传递
// 修改答案，传递第三个修改 answerId
export function submitAnswer(questionId, answer, answerId?) {
  return ppost(`/forum/answer/submit`, { questionId, answer, answerId })
}

// 对答案进行评论
export function commentAnswer(answerId, comment, repliedCommentId?) {
  return ppost(`/forum/answer/comment`, { answerId, comment, repliedCommentId })
}

// 删除答案的评论
export function commentAnswerDel(commentId) {
  return ppost(`/forum/answer/delete/comment/${commentId}`)
}

// 获取所有的提问标签
export function loadTag() {
  return pget('/forum/question/tag/load');
}

// 提交问题
export function submitQuestion(param){
  return ppost(`/forum/question/submit`, param);
}

// 搜索问题
export function searchQuestion(content, page) {
  return ppost(`/forum/question/search?page=${page}`, {query:content})
}
<file_sep>/src/modules/forum/commons/DialogHead/DialogHead.tsx
import * as React from "react"
import AssetImg from "../../../../components/AssetImg";
import "./DialogHead.less"

// 问题概要 头部组件
interface DialogHeadProps {
  leftImgUrl: string;
  user: string;
  time: string;
  rightImgUrl?: string;
  rightContent?: string;
  rightContentFunc?: object;
  // 当右侧值为下面值是，显示为灰色
  disableContentValue?: string;
}
interface DialogHeadStates {
  changeRightContent: string;
}
export default class DialogHead extends React.Component<DialogHeadProps, DialogHeadStates> {

  constructor() {
    super()
    this.state = { changeRightContent: '' }
  }

  componentWillMount() {
    if(this.props.rightContent) {
      this.setState({ changeRightContent: this.props.rightContent })
    }
  }

  componentWillReceiveProps(newProps) {
    if(newProps.rightContent && newProps.rightContent != this.state.changeRightContent) {
      this.setState({changeRightContent: newProps.rightContent})
    }
  }

  render() {
    const {
      leftImgUrl, user, time, rightImgUrl, rightContent, rightContentFunc = () => {}, disableContentValue = ''
    } = this.props
    const { changeRightContent } = this.state

    const renderQuestionLeft = () => {
      return (

<AssetImg url={leftImgUrl} width={32} height={32.5}/>

            {user}
            {time}

      )
    }

    const renderQuestionRight = () => {
      const renderRight = rightImgUrl || rightContent;
      const changeRightContentFunc = () => {
        let funcReturnContent = rightContentFunc()
        this.setState({ changeRightContent: funcReturnContent ? funcReturnContent : rightContent })
      }

      if(renderRight) {
        return (

            <div className={`dialog-right-content ${changeRightContent === disableContentValue ? 'disable' : ''}`}
                 onClick={() => changeRightContentFunc()}>
              {changeRightContent}

        )
      }
    }

    return (

        {renderQuestionLeft()}
        {renderQuestionRight()}

    )
  }

}
<file_sep>/src/modules/home/activitylist/ActivityListPage.tsx
import * as React from 'react'
import './ActivityListPage.less'
import { ActivityHome } from '../components/activity/ActivityHome'
import { changeTitle } from '../../../utils/helpers'
import { loadAllActivities } from '../async'
import { connect } from 'react-redux'
import { startLoad, endLoad, alertMsg, set } from 'reduxutil/actions'
import { mark } from '../../../utils/request'

@connect(state => state)
export default class ActivityListPage extends React.Component {

  constructor () {
    super()
    this.state = {
      data: [],
    }
  }

  async componentWillMount () {
    mark({ module: '打点', function: '着陆二级页', action: '打开活动列表页' })
    changeTitle('圈柚会')
    let res = await loadAllActivities()
    if (res.code === 200) {
      this.setState({
        data: res.msg,
      })
    } else {
      dispatch(alertMsg(res.msg))
    }
  }

  render () {
    return (

        {
          this.state.data.map((item, index) => {
            return (
              <ActivityHome data={item} key={index}/>
            )
          })
        }

    )
  }

}
<file_sep>/src/modules/plan/PlanList.less
@import "../../style/variable";

.plan-list-page {
  background: #fff;
  height: ~"calc(100% + 1px)";
  overflow-y: auto;
  .problem-recommendation {
    width: 100%;
    border-bottom: 2px solid @color_weak_background;
    .swiper-container {
      height: 143px;
      background-color: #FFF;
      padding-left: 15px;
    }
    .recommendation-header {
      height: 20px;
      position: relative;
      padding: 20px 0 18px 0;
      .header-title {
        line-height: 20px;
        font-size: @font_title;
        color: #666;
        &:before {
          content: "";
          width: 2px;
          height: 17px;
          margin-top: -4px;
          border-radius: 10px;
          display: inline-block;
          background-color: #666;
          margin-left: 15px;
          margin-right: 5px;
          vertical-align: middle;
        }
      }
      .operation-coupon {
        position: absolute;
        top: 12px;
        left: 100px;
        img {
          width: 131px;
        }
      }
    }
    .problem-item-show {
      margin-right: 15px;
      width: 120px;
      height: 75px;
      > .img {
        position: relative;
        border-radius: 3px;
        margin-bottom: 10px;
        overflow: hidden;
        width: 100%;
        height: 100%;
        background-repeat: no-repeat;
        background-size: auto 130%;
        background-position: 100% 10%;
        position: relative;
        .problem-background-electronic();
        .complete-person {
          position: absolute;
          line-height: 12px;
          height: 12px;
          right: 5px;
          bottom: 8px;
          z-index: 1;
          .icon-person {
            float: left;
            position: relative;
            top: 0;
            display: inline-block;
            width: 12px;
            height: 12px;
            background: url("https://static.iqycamp.com/images/fragment/electornic-person-icon_5.png?imageslim");
            background-size: 12px 12px;
          }
          .completed-person-count {
            float: left;
            position: relative;
            top: 1px;
            display: inline-block;
            height: 12px;
            line-height: 12px;
            font-size: 12px;
            color: #fff;
          }
        }
        > img {
          position: absolute;
          right: 0;
          top: 0;
        }
      }

      span {
        display: block;
        font-size: 1.2rem;
        line-height: 1.2rem;
        white-space: normal;
        color: @color_context;
      }
    }
    .found-more {
      margin-left: 15px;
      padding-top: 30px;

      width: 50px;
      position: relative;
      .tips-word {
        width: 1rem;
        font-size: @font_click;
        color: @color_title;
        display: inline-block;
        line-height: 1.3rem;
        -webkit-transform: translateY(-50%);
        -moz-transform: translateY(-50%);
        -ms-transform: translateY(-50%);
        -o-transform: translateY(-50%);
        transform: translateY(-50%);
        position: absolute;
        top: 50%;
        left: 5px;
      }
      .icon-next {
        position: absolute;
        top: 50%;
        right: 0;
        -webkit-transform: translateY(-50%);
        -moz-transform: translateY(-50%);
        -ms-transform: translateY(-50%);
        -o-transform: translateY(-50%);
        transform: translateY(-50%);
      }
    }
  }

  .plp-running {
    border-bottom: 1px solid @color_weak_background;
    .p-r-header {
      padding: 20px 0 0;
      height: 20px;
      .p-r-h-title {
        line-height: 20px;
        font-size: @font_title;
        color: #666;
        &:before {
          content: "";
          width: 2px;
          height: 17px;
          margin-top: -4px;
          border-radius: 10px;
          display: inline-block;
          background-color: #666;
          margin-left: 15px;
          margin-right: 5px;
          vertical-align: middle;
        }
      }
    }
    .plp-empty-container {
      margin: 20px 0;
      text-align: center;
      .plp-empty-img {

      }
      .plp-empty-text {
        font-size: @font_click;
        color: @color_title;
      }
      .plp-empty-button {
        height: 13px;
        font-size: @font_click;
        color: #FFFFFF;
        display: inline-block;
        line-height: 13px;
        padding: 5px 0;
        width: 67px;
        -webkit-border-radius: 5px;
        -moz-border-radius: 5px;
        border-radius: 5px;
        background-color: @color_strong;
        margin-top: 3px;

      }
    }

    .problem-block {
      margin: 0 auto;
      transition: transform 1s;
      &:first-child {
        margin-top: 0;
      }
      &:last-child {
        margin-bottom: 0;
      }
      //&.last {
      //  margin-bottom: 0;
      //}
      //&.first {
      //  margin-top: 0;
      //}
      .problem-item {
        background-color: #FFF;
        padding: 20px 15px;
        position: relative;
        border-bottom: 1px solid @color_weak_background;
        &:after {
          content: '';
          clear: both;
          display: block;
        }
        .problem-item-pic {
          width: 11rem;
          height: 75px;
          display: inline-block;
          overflow: hidden;
          position: relative;
          vertical-align: top;
          float: left;
          -webkit-border-radius: 3px;
          -moz-border-radius: 3px;
          border-radius: 3px;
          .problem-background-electronic();
          .problem-item-pic-img {
            width: auto;
            height: 100%;
            position: absolute;
            right: 0;
            top: 0;
          }
          .complete-person {
            position: absolute;
            line-height: 12px;
            height: 12px;
            right: 5px;
            bottom: 8px;
            z-index: 1;
            .icon-person {
              float: left;
              position: relative;
              top: 0;
              display: inline-block;
              width: 12px;
              height: 12px;
              background: url("https://static.iqycamp.com/images/fragment/electornic-person-icon_5.png?imageslim");
              background-size: 12px 12px;
            }
            .completed-person-count {
              float: left;
              position: relative;
              top: 1px;
              display: inline-block;
              height: 12px;
              line-height: 12px;
              font-size: 12px;
              color: #fff;
            }
          }
        }
        .problem-item-text {
          width: ~"calc(100% - 11rem)";
          height: 75px;
          display: inline-block;
          position: relative;

          > div {
            position: relative;
          }
          .problem-item-text-title {
            font-size: 1.5rem;
            line-height: 1.5rem;
            color: #333;
            word-wrap: break-word;
            padding: 0 0 0 15px;
          }
          .problem-item-text-done {
            font-size: 1.2rem;
            line-height: 1.2rem;
            color: #666;
            word-wrap: break-word;
            margin-top: 5px;
            padding: 0 0 0 15px;
          }
          .problem-item-text-close {
            font-size: 1.2rem;
            line-height: 1.2rem;
            color: #666;
            word-wrap: break-word;
            margin-top: 5px;
            padding: 0 0 0 15px;
          }
          .running-problem-button {
            position: absolute;
            width: 60px;
            height: 24px;
            background-image: url("https://static.iqycamp.com/images/fragment/go_learn_1.png?imageslim");
            background-size: 100% 100%;
            right: 0;
            bottom: 0;
            font-size: 13px;
            line-height: 24px;
            text-align: center;
            color: #FFFFFF;
            border-radius: 3px;

            &.lock {
              background-image: none;
              background-color: #CCCCCC;
            }
          }
        }
      }
    }
  }

  .problem-camp {
    border-bottom: 1px solid @color_weak_background;
    .camp-header {
      height: 20px;
      position: relative;
      padding: 20px 0 0 0;
      .header-title {
        line-height: 20px;
        font-size: @font_title;
        color: #666;
        &:before {
          content: "";
          width: 2px;
          height: 17px;
          margin-top: -4px;
          border-radius: 10px;
          display: inline-block;
          background-color: #666;
          margin-left: 15px;
          margin-right: 5px;
          vertical-align: middle;
        }
      }
    }
    .problem-block {
      margin: 0 auto;
      transition: transform 1s;
      &:first-child {
        margin-top: 0;
      }
      &:last-child {
        margin-bottom: 0;
      }
      //&.last {
      //  margin-bottom: 0;
      //}
      //&.first {
      //  margin-top: 0;
      //}
      .problem-item {
        background-color: #FFF;
        padding: 20px 15px;
        position: relative;
        border-bottom: 1px solid @color_weak_background;
        &:after {
          content: '';
          clear: both;
          display: block;
        }
        .problem-item-pic {
          width: 11rem;
          height: 75px;
          display: inline-block;
          overflow: hidden;
          position: relative;
          vertical-align: top;
          float: left;
          -webkit-border-radius: 3px;
          -moz-border-radius: 3px;
          border-radius: 3px;
          .problem-background-electronic();
          .problem-item-pic-img {
            width: auto;
            height: 100%;
            position: absolute;
            right: 0;
            top: 0;
          }
          .complete-person {
            position: absolute;
            line-height: 12px;
            height: 12px;
            right: 5px;
            bottom: 8px;
            z-index: 1;
            .icon-person {
              float: left;
              position: relative;
              top: 0;
              display: inline-block;
              width: 12px;
              height: 12px;
              background: url("https://static.iqycamp.com/images/fragment/electornic-person-icon_5.png?imageslim");
              background-size: 12px 12px;
            }
            .completed-person-count {
              float: left;
              position: relative;
              top: 1px;
              display: inline-block;
              height: 12px;
              line-height: 12px;
              font-size: 12px;
              color: #fff;
            }
          }
        }
        .problem-item-text {
          width: ~"calc(100% - 11rem)";
          height: 75px;
          display: inline-block;
          position: relative;

          > div {
            position: relative;
          }
          .problem-item-text-title {
            font-size: 1.5rem;
            line-height: 1.5rem;
            color: #333;
            word-wrap: break-word;
            padding: 0 0 0 15px;
          }
          .problem-item-text-done {
            font-size: 1.2rem;
            line-height: 1.2rem;
            color: #666;
            word-wrap: break-word;
            margin-top: 5px;
            padding: 0 0 0 15px;
          }
          .problem-item-text-close {
            font-size: 1.2rem;
            line-height: 1.2rem;
            color: #666;
            word-wrap: break-word;
            margin-top: 5px;
            padding: 0 0 0 15px;
          }
          .running-problem-button {
            position: absolute;
            width: 60px;
            height: 24px;
            background-image: url("https://static.iqycamp.com/images/fragment/go_learn_1.png?imageslim");
            background-size: 100% 100%;
            right: 0;
            bottom: 0;
            font-size: 13px;
            line-height: 24px;
            text-align: center;
            color: #FFFFFF;
            border-radius: 3px;

            &.lock {
              background-image: none;
              background-color: #CCCCCC;
            }
          }
        }
      }
    }
  }

  .plp-completed {
    min-height: 30rem;
    border-bottom: 2px solid @color_weak_background;
    .p-c-header {
      height: 20px;
      padding: 20px 0 0;
      .p-c-h-title {
        line-height: 20px;
        font-size: @font_title;
        color: #666;
        &:before {
          content: "";
          width: 2px;
          height: 17px;
          margin-top: -4px;
          border-radius: 10px;
          display: inline-block;
          background-color: #666;
          margin-left: 15px;
          margin-right: 5px;
          vertical-align: middle;
        }
      }
    }
    .complete-plan-empty {
      background-color: @color_white;
      img {
        width: ~"calc(100% - 104px)";
        margin-left: 52px;
      }
    }
    .p-c-container {
      &.none {
        background: #FFF;
      }
      .p-c-c-left {
        width: 56px;
        height: 100%;
        float: left;
      }
      .p-c-c-right {
        .p-c-c-r-block {
          position: relative;
          background-color: #FFFFFF;
          .p-c-b-pic {
            display: inline-block;
            overflow: hidden;
            position: relative;
            vertical-align: top;
            float: left;
            width: 84px;
            height: 53px;
            -webkit-border-radius: 3px;
            -moz-border-radius: 3px;
            border-radius: 3px;
            .problem-background-electronic(40px, 12%);
            .p-c-b-p-img {
              width: auto;
              height: 100%;
              position: absolute;
              right: 0;
              top: 0;
            }
          }
          .p-c-b-text {
            position: relative;
            padding: 20px 15px;
            border-bottom: 1px solid @color_weak_background;
            .p-c-b-t-left {
              position: relative;
              max-width: 90%;
              .p-c-b-t-l-title {
                font-size: 1.5rem;
                line-height: 1.5rem;
                color: #333;
              }
              .p-c-b-t-l-close {
                font-size: 1.2rem;
                line-height: 1.2rem;
                color: #666;
              }
            }
            .p-c-b-t-right {
              position: absolute;
              right: 5%;
              top: 50%;
              font-size: 1.5rem;
              color: #333;
              transform: translateY(-50%);
            }
          }
        }
      }
    }
  }
}

.problem-background-electronic(@imgSize: 60px, @catalogTop: 18%) {
  .problem-item-backcolor {
    width: 100%;
    height: 100%;
    position: absolute;
    background-size: 100% 100% !important;
    border-radius: 5px;
    &.catalog1 {
      background: -webkit-gradient(linear, 0 top, 0 bottom, from(#71d2aa), to(#4aa680)) !important;
    }
    &.catalog2 {
      background: -webkit-gradient(linear, 0 top, 0 bottom, from(#e8cb5e), to(#ea9f00)) !important;
    }
    &.catalog3 {
      background: -webkit-gradient(linear, 0 top, 0 bottom, from(#e7a674), to(#e96a06)) !important;
    }
    &.catalog4 {
      background: -webkit-gradient(linear, 0 top, 0 bottom, from(#6bd2ea), to(#05789c)) !important;
    }
    &.catalog5 {
      background: -webkit-gradient(linear, 0 top, 0 bottom, from(#a0b5eb), to(#376cd5)) !important;
    }
  }
  .problem-item-backimg {
    width: @imgSize;
    height: @imgSize;
    left: ~"calc(50% - @{imgSize} / 2)";
    top: ~"calc(50% - @{imgSize} / 2)";
    position: absolute;
    background-size: 100% 100% !important;
    &.catalog1 {
      background: url("https://static.iqycamp.com/images/fragment/back_img_green_1.png?imageslim");
    }
    &.catalog2 {
      background: url("https://static.iqycamp.com/images/fragment/back_img_yellow_1.png?imageslim");
    }
    &.catalog3 {
      background: url("https://static.iqycamp.com/images/fragment/back_img_orange_1.png?imageslim");
    }
    &.catalog4 {
      background: url("https://static.iqycamp.com/images/fragment/back_img_blue_1.png?imageslim");
    }
    &.catalog5 {
      background: url("https://static.iqycamp.com/images/fragment/back_img_purple_1.png?imageslim");
    }
  }
  .problem-item-subCatalog {
    width: 100%;
    color: #fff;
    position: absolute;
    text-align: center;
    font-size: 2.0rem;
    line-height: 2.0rem;
    top: 50%;
    transform: translateY(-50%);
  }
}
<file_sep>/src/modules/base/Base.tsx
import * as React from 'react'
import { connect } from 'react-redux'
import { isPending, fixIosShimoBug } from 'utils/helpers'
import { Toast, Dialog } from 'react-weui'
import { set, alertMsg } from 'reduxutil/actions'
import { config } from '../helpers/JsConfig'
import AssetImg from '../../components/AssetImg'

const P = 'base'
const LOAD_KEY = `${P}.loading`
const SHOW_MODAL_KEY = `${P}.showModal`
const { Alert } = Dialog
import { toLower, get, merge } from 'lodash'
import { pget } from 'utils/request'
import Activity from '../../components/Activity'
// import UA from 'ua-device'
import './Base.less'
import $ from 'jquery'
import RequestComponent from '../../components/requestproxy/RequestComponent'

require('../../components/progress/circle-progress.js')
import { sa } from '../../utils/helpers'

$.fn.extend({
  animateCss: function(animationName, callback) {
    var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend'
    this.addClass('animated ' + animationName).one(animationEnd, function() {
      $(this).removeClass('animated ' + animationName)
      if(callback) callback()
    })
    return this
  },
})

@connect(state => state)
export default class Main extends React.Component<any, any> {

  constructor() {
    super()
    this.state = {
      alert: {
        buttons: [
          {
            label: '我知道了',
            onClick: this.closeAnswer.bind(this),
          },
        ],
      },
      windowsClient: false,
      activityMsg: false,
      showPage: false,
    }
    // window.ENV.Detected = new UA(window.navigator.userAgent)
    window.ENV.osName = toLower(get(window, 'ENV.Detected.os.name'))
    window.ENV.osVersion = toLower(get(window, 'ENV.Detected.os.version.original'))
    window.ENV.systemInfo = window.ENV.osName + ':' + window.ENV.osVersion
    // fix ios系统进入石墨文档后再返回会触发签名失败的bug
    fixIosShimoBug()
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired,
  }

  async componentWillMount() {
    let userInfoResult = await pget('/rise/customer/info')
    if(userInfoResult.code === 200) {
      window.ENV.riseId = userInfoResult.msg.riseId;
      window.ENV.className = userInfoResult.msg.className;
      window.ENV.groupId = userInfoResult.msg.groupId;
      window.ENV.roleName = userInfoResult.msg.roleName;
      window.ENV.userName = userInfoResult.msg.nickname;
      window.ENV.headImgUrl = userInfoResult.msg.headimgurl;
      window.ENV.isAsst = userInfoResult.msg.isAsst;
    }

    sa.init({
      heatmap_url: 'https://static.sensorsdata.cn/sdk/1.9.13/heatmap.min.js',
      name: 'sa',
      web_url: `https://quanwai.cloud.sensorsdata.cn/?project=${window.ENV.sensorsProject}`,
      server_url: `https://quanwai.cloud.sensorsdata.cn:4006/sa?token=<PASSWORD>&project=${window.ENV.sensorsProject}`,
      heatmap: {},
      is_single_page: true,
      show_log: false
    });
    if(!!userInfoResult.msg.riseId) {
      sa.login(userInfoResult.msg.riseId);
    }
    let props = { roleName: window.ENV.roleName, isAsst: window.ENV.isAsst, platformType: 2 };
    if(!!window.ENV.className && !!window.ENV.groupId) {
      merge(props, {
        className: window.ENV.className,
        groupId: window.ENV.groupId
      });
    }
    if(!!userInfoResult.msg.riseId) {
      merge(props, {
        riseId: userInfoResult.msg.riseId
      });
    }
    sa.registerPage(props);
    sa.quick('autoTrack');

    this.setState({ showPage: true })
    if(window.location.href.indexOf('/rise/static/guest/') === -1) {
      // 不是guest页面，判断这个用户是否可以看到活动提示
      pget('/rise/index/msg').then(res => {
        if(res.msg) {
          const { url, message } = res.msg
          this.setState({ activityMsg: true, url, message })
        }
      })
      pget(`/rise/customer/global/notify`).then(res => {
        if(res.code === 200) {
          this.setState({
            // showGlobalNotify: res.msg.showGlobalNotify,
            expiredInSevenDays: res.msg.expiredInSevenDays,
            expired: res.msg.expired,
          })
        }
      })
    }
  }

  componentWillUpdate() {
    //windows客户端显示返回按钮
    // if(navigator.userAgent.indexOf('WindowsWechat') !== -1) {
    //   //排除不显示返回按钮的页面
    //   if(window.location.pathname !== '/rise/static/rise'
    //     && window.location.pathname !== '/rise/static/camp'
    //     && window.location.pathname !== '/rise/static/practice/warmup') {
    //     if(!this.state.windowsClient) {
    //       this.setState({ windowsClient: true })
    //     }
    //   } else {
    //     if(this.state.windowsClient) {
    //       this.setState({ windowsClient: false })
    //     }
    //   }
    // }
  }

  componentDidMount() {
    config([ 'chooseWXPay' ])
  }

  closeAnswer() {
    const { dispatch } = this.props
    dispatch(set(SHOW_MODAL_KEY, false))
  }

  handleClickGoRisePay() {
    window.location.href = `/pay/rise`
  }

  render() {
    if(!this.state.showPage) {
      return

    }

    const { showGlobalNotify = false, expiredInSevenDays, expired } = this.state

    const renderGlobalNotify = () => {
      if(showGlobalNotify) {
        if(expiredInSevenDays) {
          return
 this.handleClickGoRisePay()} className="global-notify expire"/>
        } else if(expired) {
          return
 this.handleClickGoRisePay()} className="global-notify expired"/>
        }
      }
    }

    return (
      <div className={`${isPending(this.props, LOAD_KEY) ? 'over-hidden' : ''}`}>
        <RequestComponent/>
        {renderGlobalNotify()}
        {this.props.children}
        <Toast show={isPending(this.props, LOAD_KEY)} icon="loading">

加载中...

        </Toast>
        <Alert {...this.state.alert} show={this.props.base.showModal}>

        </Alert>
        {
          this.state.windowsClient &&

 window.history.back()}>
            <AssetImg type="back_button" width={30} height={30} style={{ opacity: 0.3 }}/>

        }
        {
          this.state.activityMsg && this.state.message &&
          <Activity url={this.state.url} message={this.state.message}/>
        }

    )
  }
}
<file_sep>/src/components/FullScreenDialog.tsx
import * as React from 'react';
import "./FullScreenDialog.less";
import { unScrollToBorder } from '../utils/helpers'

export default class FullScreenDialog extends React.Component<any, any> {
  constructor(props) {
    super(props);
    this.state = {
      hash: '',
      level: 0,
    }
  }

  componentWillMount() {
    window.addEventListener('popstate', (e) => {
      let level = e.state.level
      if(!level) {
        level = 0
      }
      if(level < this.state.level) {
        if(this.props.close) {
          this.props.close()
        }
      }
    })

    let hash = '#next'
    if(this.props.hash) {
      hash = this.props.hash
    }

    history.pushState({ level: this.props.level }, '', hash)
    this.setState({ hash, level: this.props.level })
  }

  componentDidMount() {
    this.setState({ removeScrollLimit: unScrollToBorder('.full-screen-dialog') });
  }

  componentWillUnmount() {
    const { removeScrollLimit } = this.state;
    if(removeScrollLimit) {
      removeScrollLimit();
    }
  }

  render() {
    return (

        {this.props.children}

    )
  }
}
<file_sep>/src/modules/practice/common/ApplicationSubmit/ApplicationSubmit.less
.application-submit-container {
  .editor {
    margin: 2rem;
  }
  .bottom-tip {
    color: #999;
    font-size: 1.1rem;
    text-align: center;
  }
}
<file_sep>/src/modules/plan/EventWall.less
@import "../../style/variable";

.layout {
  width: 100%;
  overflow: hidden;
  .event-wall-tab {
    .navbar {
      width: 100%;
      box-sizing: border-box;
      display: inline-flex;
      flex-direction: row;
      justify-content: space-around;
      font-size: @font_context;
      padding: 15px;
      border-bottom: 1px solid @color_weak_background;
      .navbar-item {
        display: inline-flex;
        text-align: center;
        color: @color_title;
        &.active {
          color: @color_strong;
          span {
            border-bottom: 2px solid @color_strong;
          }
        }
      }
    }
  }
  .event-tab-body {
    overflow-y: scroll;
    -webkit-overflow-scrolling: touch;
  }
  .padding {
    height: 50px;
  }
  .event-list {
    color: @color_context;
    list-style: none;
    .event-item {
      overflow: hidden;
      padding: 15px;
      //height:65px;
      position: relative;
      border-bottom: 1px solid @color_weak_background;
      .head-pic {
        display: inline-block;
        width: 74px;
        height: 65px;
        position: relative;
        overflow: hidden;
        //position: absolute;
        //left:15px;
        //top:50%;
        //-webkit-transform: translateY(-50%);
        //-moz-transform: translateY(-50%);
        //-ms-transform: translateY(-50%);
        //-o-transform: translateY(-50%);
        //transform: translateY(-50%);
        img {
          width: auto;
          height: 100%;
          position: absolute;
          top: 0;
          right: 0;
        }
      }
      .event-info {
        display: inline-block;
        vertical-align: top;
        margin-left: 15px;
        width: ~'calc(100% - 90px)';
        .title {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
          height: 15px;
          line-height: 15px;
          font-size: 15px;
          -ms-word-break: break-all;
          word-break: break-all;
          color: @color_context;
        }
        .password {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
          line-height: 12px;
          margin-top: 6px;
          height: 12px;
          font-size: 12px;
          color: @color_title;
        }
        .describe {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
          line-height: 12px;
          font-size: 12px;
          height: 12px;
          color: @color_title;
        }
        .time {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
          line-height: 10px;
          height: 10px;
          margin-top: 6px;
          font-size: 10px;
          color: @color_help;
        }
      }
    }
  }
}
<file_sep>/src/modules/customer/ProblemGallery.less
@import "../../style/variable";

.problem-gallery{
  position: relative;
  width: 100%;
  height:100%;
  overflow-y: scroll;
  background-color: @color_weak_background;
  > .problem-galley-header{
    height:50px;
    line-height:50px;
    color:@color_title;
    padding-left:15px;
    font-size:13px;
    background-color:#ffffff;
    border-bottom: 1px solid @color_weak_background;
    &.arrow{
      &:after {
        content: ' ';
        display: inline-block;
        background: url('https://static.iqycamp.com/images/mobilePersonalEnter.png?imageslim') no-repeat right center / 6px 11.5px;
        width: 6px;
        height: 11.5px;
        float: right;
        margin-top: 19.25px;
        margin-left: 4px;
        margin-right:20px;
      }
    }

    > .header-content.arrow{
      &:after {
        content: ' ';
        display: inline-block;
        background: url("https://static.iqycamp.com/images/mobilePersonalEnter.png?imageslim") no-repeat right center/ 6px 11.5px;
        width: 6px;
        height: 11.5px;
        float: right;
        margin-top: 19.25px;
        margin-left: 4px;
      }
    }
  }
  > .problem-galley-container{
    > .galley-module{
      background-color:#ffffff;
      > .galley-module-header{
        height:45px;
        border-bottom: 1px solid @color_weak_background;
        margin-right:20px;
        > .label{
          color:@color_title;
          font-size:13px;
          padding-left:15px;
          line-height:45px;
          &:before {
            content: '';
            width: 3px;
            height: 15px;
            border-radius: 10px;
            display: inline-block;
            background-color: @color_strong;
            margin-right: 5px;
            vertical-align: middle;
            margin-top: -3px;
          }
        }
      }
      > .galley-module-content{
        > .item{
          height:45px;
          line-height:45px;
          padding-left:15px;
          border-bottom: 1px solid @color_weak_background;
          margin-right:20px;
          overflow: hidden;
          &:last-child{
            margin-right:0;
            padding-right:20px;
          }
          > .item-label{
            float: left;
            font-size:13px;
            color:@color_context;
          }
          > .item-label-none{
            float: left;
            font-size:13px;
            color:@color_help;
          }
          > .item-content{
            float: right;
            font-size:13px;
            color:@color_context;
            &:after {
              content: ' ';
              display: inline-block;
              background: url("https://static.iqycamp.com/images/mobilePersonalEnter.png?imageslim") no-repeat right center/ 6px 11.5px;
              width: 6px;
              height: 11.5px;
              float: right;
              margin-top: 16.75px;
              margin-left: 4px;
            }
          }
        }
      }
    }
  }
}
<file_sep>/src/components/RiseCell.tsx
import * as React from 'react'
import _ from "lodash"
import "./RiseCell.less";
import { Cells, Cell, CellBody, CellFooter } from "react-weui";
import DropDownList from  "../modules/customer/components/DropDownList";

interface CellProps {
  title: string,
  type: string,
  placeholder?: string,
  unit?: string,
  access?: boolean,
  onChange?: any,
  value?: any,
  dropGroup?: {
    level: number,
    data: [{id: number,value: string}],
    userData: [{id: number,value: string}],
    onChoose: any,
  },
}
export default class RiseCell extends React.Component<CellProps,any> {
  constructor(props) {
    super(props);
    this.state = {}
  }

  render() {
    const { type, title, placeholder, dropGroup, unit, access, value } = this.props;
    const renderBody = () => {
      switch(type) {
        case 'input': {
          return (
            <input id="target" placeholder={`${placeholder}`} value={value}
                   onChange={(e)=>this.props.onChange(e.currentTarget.value)} type="text"/>
          )
        }
        case 'drop': {
          let level = dropGroup.level;
          let data = dropGroup.data;
          let userData = dropGroup.userData;
          return <DropDownList level={level} data={data} placeholder={placeholder}
                               userData={userData  && userData.length>0 && userData[0].id?userData:null}
                               onChoice={(one,two)=>this.props.dropGroup.onChoose(one,two)}/>
        }
      }
    }
    const hasValue = () => {
      switch(type) {
        case 'input':
          return value;
        case 'drop':
          return _.get(dropGroup, 'userData[0].id');
      }
    }
    return (
      <Cell className={`rise-cell ${access?'access':type==='drop'?'access':''}`}>
        <CellBody className="rise-cell-body">
          {title}
        </CellBody>
        <CellFooter className={`rise-cell-footer ${hasValue()?'has-value':''}`}>
          {renderBody()} {unit}
        </CellFooter>
      </Cell>
    )
  }
}
<file_sep>/src/components/SlideSideBar.tsx
import * as React from 'react'

interface SlideSideBarProps {
}
interface SlideSideBarState {
}
export default class SlideSideBar extends React.Component<SlideSideBarProps, SlideSideBarState> {

  constructor() {
    super()
  }

  shouldComponentUpdate(nextProps, nextState) {
    return JSON.stringify(nextProps) != JSON.stringify(this.props) || JSON.stringify(nextState) != JSON.stringify(this.state)
  }

  render() {

    return (

    )
  }

}
<file_sep>/src/modules/forum/question/QuestionAnswer.less
@import "../variable";

.answer-container {
  .answer-page {
    position: relative;
    padding: 15px 15px 0;

    .answer-head-topic {
      font-size: 1.7em;
      color: @color_context;
    }

    .grey-banner {
      margin: 0 -15px;
      background-color: @color_weak_background;
    }

    .ques-desc {
      margin-top: 15px;

      .ques-content {
        margin-top: 20px;
        color: @color_title;
        font-size: 1.6em;
        line-height: 1.6em;
      }
    }

    .answer-tips {
      color: @color_help;
      background-color: @color_weak_background;
      margin: 0 -15px;
      padding: 15px;

      span {
        font-size: @font_click;
        line-height: 1.9em;
        display: block;
      }
    }

    .answer-list {
      height: 100%;

      .answer-desc {
        border-bottom: 1px solid @color_weak_background;
        margin: 0 -15px;
        padding: 15px 15px 30px;

        .answer-content {
          font-size: 1.6em;
          line-height: 1.6em;
          color: @color_title;
          margin-top: 15px;
          overflow: hidden;
          a {
            color:rgb(21, 126, 251);
            text-decoration:underline;
            cursor: pointer;
          }
        }
      }
    }

    .answer-editor {
      margin-top: 15px;
      padding-bottom: 120px;
    }

  }
}
<file_sep>/src/modules/promotion/grouplearning/GroupPromotionWaiting.less
@import "../../../style/variable";

.group-promotion-waiting {
  min-height: 100vh;
  background-color: #041931;
  box-sizing: border-box;
  padding-top: 7.5rem;
  .waiting-image {
    display: block;
    box-sizing: border-box;
    width: 11.2rem;
    background-image: url("@{imgPathRoot}/team_learning_partner_1.png?imageslim");
    height: 13rem;
    background-size: 100% auto;
    background-repeat: no-repeat;
    margin: 0 auto;
  }
  .waiting-tip-large {
    display: block;
    font-size: 2rem;
    color: #fff;
    line-height: 150%;
    text-align: center;
    margin-top: 3.5rem;
  }
  .waiting-tip-small {
    display: block;
    margin-top: 1rem;
    font-size: 1.2rem;
    color: #fff;
    text-align: center;

  }
  .waiting-invite {
    display: block;
    margin-top: 3.5rem;
    font-size: 1.2rem;
    color: #FFB200;
    text-align: center;
    text-decoration: underline;
  }
}
<file_sep>/src/modules/forum/commons/DialogBottom/DialogBottomIcon.less
@import "../../variable.less";

// DialogBottom
.dialog-bottom {
  position: relative;
  margin: 10px 0 15px 0;
  top: 0;
  bottom: 15px;

  .dialog-bottom-desc {
    width: 20%;
    font-size: 1.1em;
    line-height: 1.1em;
    color: @color_strong;
  }

  .dialog-bottom-icon {
    position: absolute;
    right: 0;

    .dialog-bottom-btnarea-1 {
      height: 100%;
      display: inline-block;
      margin-right: 25px;
      margin-bottom: 15px;

      .btn1-right-content {
        vertical-align: text-bottom;
        font-size: 1.4em;
        margin-left: 4px;
        color: @color_help;
      }
      div {
        display: inline-block;
      }
    }
    .dialog-bottom-btnarea-2 {
      height: 100%;
      max-width: 80px;
      display: inline-block;

      .btn2-right-content {
        vertical-align: text-bottom;
        font-size: 1.4em;
        margin-left: 4px;
        color: @color_help;
      }
      div {
        display: inline-block;
      }
    }
  }

}
<file_sep>/src/modules/forum/commons/SimpleQuestion/SimpleQuestion.tsx
import * as React from "react"
import "./SimpleQuestion.less"

interface SimpleQuestionProps {
    title: string;
    follow: number;
    answer: number;
    onclickFunc?: object;
}
export default class SimpleQuestion extends React.Component<SimpleQuestionProps, any> {

    constructor() {
        super()
    }

    render() {
        const {
            title, follow, answer, onclickFunc = () => {}
        } = this.props;

        return (

onclickFunc()}>

{title}

                    {follow}{'人关注，'}{answer}{'个回答'}

        )
    }

}
<file_sep>/src/modules/customer/account/PersonalModify.tsx
import * as React from 'react'
import { connect } from 'react-redux'
import { set, startLoad, endLoad, alertMsg } from 'reduxutil/actions'
import './PersonalModify.less'
import AssetImg from '../../../components/AssetImg'
import { mark } from '../../../utils/request'
import { MarkBlock } from '../../../components/markblock/MarkBlock'

/**
 * 个人中心头像昵称修改页
 */
@connect(state => state)
export default class PersonalModify extends React.Component {

  constructor() {
    super()
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  componentWillMount() {
    mark({
      module: '打点',
      function: '个人中心',
      action: '加载昵称头像修改页'
    })
  }

  handleClickGoNickNameModifyPage(nickName) {
    this.context.router.push({
      pathname: `/rise/static/customer/modify/nickname`
    })
  }

  handleClickGoHeadImgModifyPage(headImgUrl) {
    this.context.router.push({
      pathname: `/rise/static/customer/modify/headImg`,
      query: {
        headImgUrl: headImgUrl
      }
    })
  }

  render() {
    return (

头像

          <MarkBlock module={'打点'} func={'个人中心'} action={'点击修改头像'} className="content header-img"
                     onClick={() => this.handleClickGoHeadImgModifyPage(window.ENV.headImgUrl)}>
            <AssetImg ref="headImg" url={window.ENV.headImgUrl} alt=""/>
          </MarkBlock>

        <MarkBlock module={'打点'} func={'个人中心'} action={'点击修改昵称'} className="item"
                   onClick={() => this.handleClickGoNickNameModifyPage(window.ENV.userName)}>

昵称

{window.ENV.userName}

        </MarkBlock>

    )
  }

}
<file_sep>/src/modules/forum/question/SubmitQuestionInit.less
@import "../variable.less";

.question-init-container {
  height: 100%;
  background-color: @color_weak_background;
  overflow: auto;
  .question-page {
    width: 100%;
    height: 100%;
    &.selected {
      padding-bottom: 50px;
    }
    background-color: white;
    .page-title {
      font-size: @font_title;
      text-align: center;
      color: @color_title;
      padding: 15px 0;
    }
    .question-title {
      width: ~"calc(100% - 30px)";
      margin-left: 15px;
      .length-div {
        margin-right: 10px;
        margin-top: -25px;
        .length-tip {
          color: @color_help;
          font-size: 11px;
          text-align: right;
        }
      }
      textarea {
        border-radius: @border-radius;
        height: 74px;
        width: ~"calc(100% - 26px)";
        display: inline-block;
        resize: none;
        border: 1px solid #ccc;
        font-size: 16px;
        padding: 10px 12px;
        box-shadow: 0 0 0 rgba(0, 0, 0, 0);
        -webkit-appearance: none;
        &::-webkit-input-placeholder {
          color: @color_weak;
          line-height: @font_context;
        }
      }
    }
    .question-divide {
      padding: 0 15px;
      color: @color_title;
      height: 35px;
      line-height: 35px;
      margin-top: 15px;
      font-size: @font_click;
      border-bottom: 1px solid @color_weak_background;
      background-color: @color_weak_background;
    }
    .ques-list {
      background-color: @color_weak_background;
      .simple-ques-desc {
        padding: 10px 15px;
        background-color: #fff;
        margin-bottom: 1px;
        .simple-ques-title {
          color: @color_context;
          font-size: @font_context;
          line-height: @font_context;
          overflow: hidden;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
          -moz-box-orient: vertical;
        }
      }
    }
  }
}
<file_sep>/src/modules/home/async.tsx
import requestProxy from '../../components/requestproxy/requestProxy'

export function loadLandingPageData () {
  return requestProxy.getProxy('/rise/landing/load')
}

export function loadAllLives () {
  return requestProxy.getProxy('/rise/landing/load/lives')
}

export function loadAllActivities () {
  return requestProxy.getProxy('/rise/landing/load/activities')
}

export function loadShuffleArticles () {
  return requestProxy.getProxy('/rise/landing/load/shuffle/articles')
}
<file_sep>/src/modules/customer/RiseMember.tsx
import * as React from "react"
import {connect} from "react-redux"
import { isBoolean } from "lodash"
import {set, startLoad, endLoad, alertMsg} from "reduxutil/actions"
import {pget, ppost, mark} from "utils/request"
import {changeTitle} from "utils/helpers"
import "./PointTip.less"
import { Button, ButtonArea, Dialog, Form, FormCell, CellHeader, CellBody, Checkbox } from "react-weui"
import { MarkBlock } from '../../components/markblock/MarkBlock'

/**
 * 我的会员页面
 */
@connect(state => state)
export default class RiseMember extends React.Component<any,any> {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  constructor(props) {
    super(props);
    this.state = {}
  }

  componentWillMount() {
    mark({module: "打点", function: "个人中心", action: "打开我的会员页面"});
    changeTitle("圈外同学");
    const {dispatch} = this.props;
    dispatch(startLoad());
    pget("/rise/customer/member").then(res => {
      dispatch(endLoad());
      const {code, msg} = res;
      if (code === 200) {
        if (msg) {
          this.setState({riseMember: true, memberType: msg});
        } else {
          this.setState({riseMember: false})
        }
      } else {
        dispatch(alertMsg(msg));
      }
    }).catch(ex => {
      dispatch(endLoad());
      dispatch(alertMsg(ex));
    })
    // this.setState({riseMember: this.props.location.query.riseMember});
  }

  goUp(){
    pget('/personal/mark/rise/up').then(()=>{
      window.location.href=`/pay/rise`
    }).catch(()=>{
      window.location.href=`/pay/rise`
    })
  }

    memberDescription(memberType) {
        return (

基于个体势能模型的课程体系

圈圈用9年时间，经历6个行业50家公司400多个岗位的人才发展体系的设计和深入研究，搭建出完善的个体势能模型，用顶级公司培养人才的方式，培养你自己。

支撑碎片时间学习的移动工具

在圈外课程，每个知识点都拆成5分钟可以学习的结构，而串联起来又是极其系统的课程内容。 不管你用手机、iPad还是电脑，通勤的路上还是下班回家，都可以利用时间，学习起来。

全年50+节课&100+场学习活动

                    一个在职场中具备竞争力的人，不同阶段所需的能力，可以用个体势能模型来总结，圈外的课程就是根据此设计（过去一个月我们更新了7节，所以可能超过50节）

                    除了学习，我们还有各领域大咖直播（大咖选择标准：不看流量看干货，目前我们请到的大咖都是那些只在重要场合露脸，平时极少愿意对外宣传的行业牛人）、好评率超过99.9999%的案例吊打、帮助你累积职场资源的线下学习工作坊、游戏化学习的地域PK.......

输入+输出+讨论的刻意练习环境

                    能力的学习，跟我们去学习某个知识（比如牛奶是白色的）不同，不会只是信息理解和记忆。因为从我们知道一个方法，到真正理解它跟其它知识的联系，再到能够用它来解决问题，最终内化为自己的能力，中间需要一系列的刻意练习。

                    圈外课程分了章节，而每小节都由知识点、选择题、应用题组成。

                    其中，知识点是本节内容的讲解，选择题是将内容融入到生活工作场景中，巩固我们对内容的理解。

                    而应用题，则是真正将所学内容用于实际问题的解决。比如，让你用学到的讲故事方法，设计一段表白词，等等。

连接高质量职场资源的校友会

                    在持续成长中，每个人都离不开这三类人，自己、伙伴和导师。一个人的思考也是有限的，你需要有相同意愿、智商相近的人，与你同行。在你做的好时，给你赞扬，做得不好时，给你建议和鼓励。

                    在圈外学员中，不乏各类大厂、世界500强等企业的童鞋。所以我们在线上设立了各个地区的校友会，并且定期举办各种学习活动，组织大家一起升级打怪，在学习中找到良师益友。

优秀学员的助教&奖学金计划

                    1、圈外助教，预计每季度从优秀学员中选拔一次。这意味着什么呢？

                     1）免费学习圈外所有课程

                     2）圈圈多年职场经验，倾囊相授

                     3）全年持续全面的不定期培训，圈外有完备的助教培养体系，帮助大家加速职场发展。

                     4）跟一帮能力超强的小伙伴一起玩耍

                    2、奖学金计划，认真学习的童鞋，我们会根据学习积分情况返回一部分学费。

                    3、跟圈圈一对一交流，如你所知，对外咨询费3000+，视童鞋们的学习情况而定，每季度至少一个名额。

                    4、工作机会推荐 ，包括圈外工作机会。

                {
                    memberType === 3 ?

商学院学员优先得到作业点评和案例分析

职场中并不是每个人都那么幸运，有mentor天天来指导自己，给自己反馈。

                                【圈外同学】的学习模式是我们提供体系化课程，你负责输出，我们负责反馈，帮助你提升思维能力和方法。

                                 1）圈外专业教练点评课程。（前几天圈圈还亲自跑去大家的作业下点评呢，总之会时常有一些小惊喜喔）

                                 2）作业案例直播：针对各个课程，以学员作业为案例，进行语音直播讲解和答疑，帮助理解，俗称“吊打”。在【圈外同学】首批吊打中，参加人数超过了2000人，当天大家讨论到晚上10点半才结束。

商学院学员可以免费参加线下活动，结识伙伴&导师

线下我们会定期举办学习工作坊，很多童鞋还通过线下工作坊勾搭到行业人士，解决了自己的职业困惑。除此之外，各个分舵也会不定期举办一些线下活动，很多童鞋在这里找到了志同道合的朋友。

 :

专业版用户可参加作业案例分析

                                职场中并不是每个人都那么幸运，有mentor天天来指导自己，给自己反馈。

                                【圈外同学】的学习模式是我们提供体系化课程，你负责输出，我们负责反馈，帮助你提升思维能力和方法。

                                针对各个课程，我们会以学员作业为案例，进行语音直播讲解和答疑，帮助理解，俗称“吊打”。在【圈外同学】首批吊打中，参加人数超过了2000人，当天大家讨论到晚上10点半才结束。

专业版用户可优惠参加线下活动

                                线下我们会定期举办学习工作坊，很多童鞋还通过线下工作坊勾搭到行业人士，解决了自己的职业困惑。

                                除此之外，各个分舵也会不定期举办一些线下活动，很多童鞋在这里找到了志同道合的朋友。

                                专业版会员，可以以覆盖场地、物料等基本成本的价格，优惠参加线下学习活动。

                }

        )
    }

  renderMemberTips(){
    const {memberType,riseMember} = this.state;

    if(riseMember){
      return (

          当前会员：{memberType.name}

          微信昵称：{window.ENV.userName}

          有效期至：{memberType.endTime}

          会员专享：

          {this.memberDescription(memberType.memberTypeId)}

      )
    } else {
      if(isBoolean(riseMember)){
        return (

            还未升级成正式版哦！

              <MarkBlock module={'打点'} func={'个人中心'} action={'点击升级按钮'} className="button" onClick={()=>{this.goUp()}}>
                升级正式版
              </MarkBlock>

        )
      } else {
        return null;
      }

    }
  }

  render() {
    return (

          【圈外同学】正式版

        {this.renderMemberTips()}

    )
  }
}
<file_sep>/src/modules/customer/PointTip.tsx
import * as React from "react"
import {connect} from "react-redux"
import {set, startLoad, endLoad, alertMsg} from "redux/actions"
import {changeTitle} from "utils/helpers"
import "./PointTip.less"
import { mark } from 'utils/request'

@connect(state => state)
export default class PointTip extends React.Component<any,any> {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  constructor(props) {
    super(props);
    this.state = {
      point: null,
    }
  }

  componentWillMount() {
    changeTitle("积分规则");
    mark({module: "打点", function: "帮助", action: "打开积分规则"})
  }

  render() {
    return (

          积分规则

          如何获得积分？

          -完善个人信息，获得30积分

          -完成选择题，根据答对题目的难度不同，获得20/30/50积分

          -完成选做的应用题，根据难度获得对应的40/60/100积分

          -应用题每获得一个点赞加2积分，优秀的作业更易收到点赞

          积分有什么用？

          -积分换购、延长开放期等功能开发中，敬请关注

    )
  }
}
<file_sep>/src/modules/forum/question/QuestionAnswer.tsx
import * as React from "react";
import "./QuestionAnswer.less";
import { connect } from "react-redux"
import { DialogHead, DialogBottomBtn, DialogBottomIcon, ForumButton } from "../commons/ForumComponent";
import { approveAnswer, disApproveAnswer, disFollow, follow, getQuestion, submitAnswer } from "../async";
import { mark } from "../../../utils/request"
import Editor from "../../../components/simditor/Editor";
import { splitText, removeHtmlTags, scroll, changeTitle } from "../../../utils/helpers"
import { startLoad, endLoad, alertMsg } from "../../../redux/actions";
import AnswerComment from "./AnswerComment"
import FullScreenDialog from "../../../components/FullScreenDialog"
import PullSlideTip from '../../../components/PullSlideTip'
import RenderInBody from '../../../components/RenderInBody'

interface QuestionAnswerStates {
  question: object;
  // 弹出答案书写框
  questionWritable: boolean;
  btn1Content: string;
  btn2Content: string;
  answerList: object;
  // 是否是新回答
  submitNewAnswer: boolean;
  // 自己的答案
  myAnswer: object;
  selfAnswerContent: string;
  // 提示高度
  answerTipsHeight: number;
  // 待预览图片
  previewImgs: object;
  // 回答id
  answerId: number;
}
let isExpandQuestion = false;

@connect(state => state)
export default class QuestionAnswer extends React.Component<any, QuestionAnswerStates> {

  constructor() {
    super()
    this.state = {
      question: {},
      questionWritable: false,
      btn1Content: '',
      btn2Content: '',
      answerList: [],
      submitNewAnswer: false,
      myAnswer: {},
      selfAnswerContent: '',
      answerTipsHeight: '90',
      previewImgs: [],
      answerId: -1,
    }
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  componentWillMount() {
    changeTitle('论坛')
    let questionId = this.props.questionId
    if(!questionId) {
      questionId = this.props.location.query.questionId
    }
    mark({ module: "打点", function: "论坛", action: "打开问题详情页", memo: questionId })

    const { dispatch } = this.props
    dispatch(startLoad())
    getQuestion(questionId).then(res => {
      const { code, msg } = res
      dispatch(endLoad())
      if(code === 200) {
        const question = msg
        msg.answerList.map((answerItem) => {
          if(answerItem.mine) {
            this.setState({ myAnswer: answerItem })
          }
        })
        this.setState({
          question: res.msg,
          btn1Content: question.follow ? '已关注' : '关注问题',
          btn2Content: question.answered ? '编辑我的回答' : '回答',
          submitNewAnswer: !question.answered,
          answerList: res.msg.answerList
        }, () => {
          let node = this.refs.quesDesc
          if(node) {
            const height = window.innerHeight - node.clientHeight - 70
            this.setState({ answerTipsHeight: height })
          }
          // 设置图片预览对象
          this.setState({ previewImgs: document.getElementsByTagName('img') })

          // 这里有坑，获取dom结构之后添加事件失败，必须用setTimeout包一下
          // TODO 怀疑是前面的state set 完之后dom还没处理完，需要研究下
          setTimeout(() => {
            // 设置answer-content
            this.bindProblem()
          }, 0)
        })
      } else {
        dispatch(alertMsg(msg))
      }
    }).catch(e => {
      dispatch(endLoad())
      dispatch(alertMsg(e))
    })
  }

  componentDidUpdate() {
    // 添加图片预览功能
    const { previewImgs } = this.state
    for(let i = 0; i < previewImgs.length; i++) {
      if(previewImgs[ i ].src.indexOf("answer-") > 0) {
        let imgSrc = previewImgs[ i ].src
        previewImgs[ i ].onclick = () => {
          wx.previewImage({ current: imgSrc, urls: [ imgSrc ] });
        }
      }
    }
  }

  // 添加新的回答
  handleClickAddNewAnswer() {
    this.setState({
      questionWritable: !this.state.questionWritable,
      btn2Content: this.state.questionWritable ? '回答' : '取消回答',
      submitNewAnswer: true
    })
  }

  // 编辑自己的回答
  handleClickEditSelfAnswer() {
    let content = '';
    if(this.state.questionWritable) {
      content = '编辑我的回答';
    } else {
      content = '取消回答';
    }
    this.setState({
      questionWritable: !this.state.questionWritable,
      btn2Content: content, submitNewAnswer: false,
      selfAnswerContent: this.state.myAnswer.answer
    })
  }

  // 关注
  handleClickFollow(questionId) {
    follow(questionId).then(() => {
      this.setState({
        btn1Content: '已关注'
      })
    })
  }

  // 取消关注
  handleClickFollowCancel(questionId) {
    disFollow(questionId)
    this.setState({
      btn1Content: '关注问题'
    })
  }

  // 自己的提问，跳转问题修改页
  handleCLickGoQuestionSubmitPage(questionId) {
    this.context.router.push({
      pathname: "/forum/static/question/init",
      query: { questionId }
    })
  }

  // 跳转到回答的评论页
  handleClickGoAnswerCommentPage(answerId) {
    this.setState({ answerId, show: true })
  }

  // 折叠或者展开答案
  handleClickExpandQuestion(description) {
    let node = this.refs.quesContent;
    if(!isExpandQuestion) {
      node.innerHTML = description
    } else {
      node.innerHTML = splitText(description, 68)
    }
    isExpandQuestion = !isExpandQuestion;
    return isExpandQuestion ? "收起" : "展开"
  }

  // 提交答案
  submitAnswer(questionId) {
    const answer = this.refs.editor.getValue();
    const { dispatch } = this.props;
    if(removeHtmlTags(answer).length > 10000) {
      dispatch(alertMsg('回答不能超过10000个字哦'));
      return;
    }
    if(!answer) {
      dispatch(alertMsg('回答不能为空哦'));
      return;
    }
    const { answerList, submitNewAnswer, myAnswer } = this.state
    dispatch(startLoad())
    if(submitNewAnswer) {
      submitAnswer(questionId, answer).then(res => {
        dispatch(endLoad())
        const { code, msg } = res
        if(code === 200) {
          this.setState({
            questionWritable: false,
            btn2Content: '编辑我的回答',
            answerList: answerList.concat(msg),
            submitNewAnswer: false,
            myAnswer: msg
          }, () => {
            scroll('#myanswer', '.answer-container');
            // document.querySelector(".answer-container").scrollTop = document.querySelector("#myanswer").offsetTop
          })
        } else {
          dispatch(alertMsg(msg))
        }
      }).catch(e => {
        dispatch(endLoad())
        dispatch(alertMsg(e))
      })
    } else {
      submitAnswer(questionId, answer, myAnswer.id).then(res => {
        dispatch(endLoad())
        const { code, msg } = res
        if(code === 200) {
          let newAnswerList = [];
          answerList.map((answerItem, idx) => {
            if(!answerItem.mine) {
              newAnswerList.push(answerItem)
            } else {
              newAnswerList.push(res.msg)
            }
          });
          this.setState({
            questionWritable: false,
            btn2Content: '编辑我的回答',
            answerList: newAnswerList,
            myAnswer: res.msg
          }, () => {
          }, () => {
            scroll('#myanswer', '.answer-container');
            // document.querySelector(".answer-container").scrollTop = document.querySelector("#myanswer").offsetTop
          })
        } else {
          dispatch(alertMsg(msg))
        }
      }).catch(e => {
        dispatch(endLoad())
        dispatch(alertMsg(e))
      })
    }
  }

  /**
   * 绑定答案超链接点击事件
   * @param node 答案文本的节点，需要data-problemid属性和data-answerid属性
   * @param questionId 本页面的quesitonid
   */
  bindProblemHrefClickHandle(node, questionId) {
    let problemHrefGroup = node.querySelectorAll('a');
    for(let idx = 0; idx < problemHrefGroup.length; idx++) {
      let problemHref = problemHrefGroup[ idx ];
      let problemId = problemHref.getAttribute('data-problemid');
      let answerId = node.getAttribute('data-answerid');
      if(problemId) {
        problemHref.addEventListener('click', () => {
          mark({ module: "论坛分享超链接", function: problemId, action: questionId, memo: answerId })
          this.context.router.push({
            pathname: '/rise/static/plan/view',
            query: { id: problemId }
          })
        });
      }
    }
  }

  bindProblem() {
    let answerContentGroup = document.querySelectorAll('.answer-content');
    if(answerContentGroup) {
      for(let idx = 0; idx < answerContentGroup.length; idx++) {
        let answerContent = answerContentGroup[ idx ];
        this.bindProblemHrefClickHandle(answerContent, this.state.question.id)
      }
    }
  }

  closeDialog() {
    this.setState({ show: false }, () => {
      this.bindProblem()
    })
  }

  render() {
    const { question, questionWritable, btn1Content, btn2Content, submitNewAnswer, answerList, answerId, show } = this.state

    const {
      addTimeStr, answerCount = 0, answered, authorHeadPic, authorUserName,
      description = '', followCount = 0, id, mine, topic
    } = question

    const renderQuestion = () => {
      if(!id) return
      return (

          <DialogHead leftImgUrl={authorHeadPic} user={authorUserName} time={addTimeStr}/>

          <DialogBottomBtn
            leftContent={removeHtmlTags(description).length > 68 ? `展开` : false}
            leftContentFunc={this.handleClickExpandQuestion.bind(this, description)}
            btn1Content={mine ? null : btn1Content} btn1DisableValue={`已关注`}
            btn1ContentFunc={
              btn1Content === '已关注' ?
                this.handleClickFollowCancel.bind(this, question.id) :
                this.handleClickFollow.bind(this, question.id)
            }
            btn2Content={mine ? '编辑我的问题' : btn2Content} btn2DisableValue={`回答`}
            btn2ContentFunc={
              mine ?
                this.handleCLickGoQuestionSubmitPage.bind(this, question.id) :
                submitNewAnswer ? this.handleClickAddNewAnswer.bind(this) : this.handleClickEditSelfAnswer.bind(this)
            }
          />

      )
    }

    const renderAnswerTips = () => {
      if(questionWritable) return;
      return (

          该问题还没有答案，你可以
          1.回答问题
          2.点击关注，有新的回答时会通知你

      )
    }

    const renderAnswers = () => {
      if(!answerList) return;
      if(questionWritable) return;
      return (

          {
            answerList.map((answerItem, idx) => {
              const { answer = '', approval, approvalCount, authorHeadPic, authorUserName, commentCount, id, publishTimeStr, mine } = answerItem
              let tag = approval
              let comment = 'https://static.iqycamp.com/images/fragment/comment.png?imageslim'
              let unvote = 'https://static.iqycamp.com/images/fragment/unvote.png?imageslim'
              let voted = 'https://static.iqycamp.com/images/fragment/voted.png?imageslim'
              let btn2ImgUrl = approval ? voted : unvote
              const changeBtn2ImgUrl = () => {
                if(tag) {
                  // 已赞同，则取消赞同
                  disApproveAnswer(id)
                } else {
                  // 还未赞同，点击赞同
                  approveAnswer(id)
                }
                tag = !tag
                return tag ? voted : unvote
              }

              let isExpand = false
              const expandComment = (idx) => {
                let commentNode = this.refs[ `ansComment${idx}` ]
                if(isExpand) {
                  commentNode.innerHTML = splitText(answer, 68);
                } else {
                  mark({ module: "打点", function: "论坛", action: "答案，点击展开", memo: answerItem.id })
                  commentNode.innerHTML = answer;
                }
                this.bindProblemHrefClickHandle(commentNode, question.id);
                isExpand = !isExpand
                return isExpand ? "收起" : "展开"
              }

              return (

                  <DialogHead leftImgUrl={authorHeadPic} user={authorUserName} time={publishTimeStr}/>
                  <div className="answer-content" ref={`ansComment${idx}`} data-answerid={answerItem.id}
                       dangerouslySetInnerHTML={{ __html: splitText(answer, 68) }}/>
                  <DialogBottomIcon
                    leftContent={removeHtmlTags(answer).length > 68 ? `展开` : false}
                    leftContentFunc={() => expandComment(idx)}
                    btn1ImgUrl={comment} btn1Content={commentCount}
                    btn1ContentFunc={this.handleClickGoAnswerCommentPage.bind(this, answerItem.id)}
                    btn2ImgUrl={btn2ImgUrl} btn2Content={approvalCount} btn2ContentFunc={changeBtn2ImgUrl}
                  />

              )
            })
          }
          <PullSlideTip isEnd={true}/>

      )
    }

    const renderAnswerWriteBox = () => {
      if(!questionWritable) return;
      let scrollContainer = '.answer-container'
      return (

          <Editor
            toolbarFloat={false}
            ref="editor" moduleId="6" maxLength="10000" scrollContainer="answer-container"
            value={this.state.myAnswer.answer}
            placeholder="回答问题时，可以试试以下的思路：
1，澄清对问题的理解；
2，分析可能的原因；
3，提供建议和解决方案；
4，说明使用的哪一门课程/知识点，帮助自己回顾学到的知识。"
          />
          <RenderInBody>
            <ForumButton content="提交" clickFunc={this.submitAnswer.bind(this, question.id)}/>
          </RenderInBody>

      )
    }

    return (

{topic}

          {renderQuestion()}

          {
            answerList.length === 0 ?
              renderAnswerTips() :
              renderAnswers()
          }
          {renderAnswerWriteBox()}

        {show ?
          <FullScreenDialog close={()=> this.closeDialog()} hash="#comment" level={2}>
            <AnswerComment answerId={answerId}/>
          </FullScreenDialog> : null}

    )
  }

}
<file_sep>/src/modules/home/components/problem/ProblemHome.less
.problem-home-component {
  box-sizing: border-box;
  position: relative;
  width: 100%;
  height: 10rem;
  margin: 2rem 0 4rem;
  .abbreviation {
    position: absolute;
    top: 0;
    left: 0;
    font-size: 1.7rem;
    height: 2.4rem;
    line-height: 2.4rem;
    color: #333333;
  }
  .problem {
    position: absolute;
    left: 0;
    top: 3.5rem;
    font-size: 1.3rem;
    color: #333333;
  }
  .hot {
    position: absolute;
    font-size: 1.3rem;
    color: #ff983f;
    left: 12rem;
    height: 2.4rem;
    line-height: 2.4rem;
  }
  .order {
    position: absolute;
    box-sizing: border-box;
    left: 0;
    bottom: 0;
    width: 10rem;
    height: 3rem;
    line-height: 3rem;
    border-radius: 100px;
    font-size: 1.2rem;
    text-align: center;
    color:#ff983f;
    border: 1px solid #ff983f;
  }
  .thumbnail {
    position: absolute;
    width: 10rem;
    height: 10rem;
    right: 0;
    top: 0;
    border-radius: 4px;
  }
}
<file_sep>/src/modules/customer/account/Account.tsx
import * as React from 'react'
import { connect } from 'react-redux'
import { set, startLoad, endLoad, alertMsg } from 'reduxutil/actions'
import { mark } from 'utils/request'
import { changeTitle, unScrollToBorder } from 'utils/helpers'
import './Account.less'
import { MarkBlock } from '../../../components/markblock/MarkBlock'
import { loadUserAccount } from '../async'

/**
 * 我的账户页面
 */
@connect(state => state)
export default class Rise extends React.Component<any, any> {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  constructor(props) {
    super(props)
    this.state = {
      data: {}
    }
  }

  componentWillMount() {
    mark({ module: '打点', function: '个人中心', action: '打开我的账户页面' })
    changeTitle('我的账户')
    const { dispatch } = this.props
    dispatch(startLoad())
    loadUserAccount().then(res => {
      dispatch(endLoad())
      if(res.code === 200) {
        this.setState({ data: res.msg }, () => {
          unScrollToBorder('.coupon-box')
        })
      } else {
        dispatch(alertMsg(res.msg))
      }
    }).catch(err => {
      dispatch(endLoad())
      dispatch(alertMsg(err + ''))
    })
  }

  handleClickGoMemberDesc() {
    const memberType = this.state.data.memberType
    switch(memberType) {
      case '专项课':
        window.location.href = 'https://shimo.im/doc/zPvwOCCxqygcof0B?r=L8QE82/'
        break
      default:
        this.context.router.push('/rise/static/customer/member')
        break
    }
  }

  goMobileCheck() {
    this.context.router.push('/rise/static/customer/mobile/check')
  }

  render() {
    const { data } = this.state
    const { riseId, memberType, mobile, nickName, memberId, coupons = [] } = data

    const renderCoupons = () => {
      if(coupons.length === 0) {
        return (

奖学金/优惠券

暂无

        )
      } else {
        return (

奖学金/优惠券

              {
                coupons.map((coupon, index) => {
                  let jxjSrc = 'https://static.iqycamp.com/images/fragment/person_coupon_jxj.png'
                  let yhqSrc = 'https://static.iqycamp.com/images/fragment/person_coupon_yhq.png'
                  let imgSrc = (coupon.description && coupon.description.indexOf('奖学金')) >= 0 ? jxjSrc : yhqSrc
                  return (

{coupon.description} ￥{coupon.amount} 元

{coupon.expiredDateString}到期

                  )
                })
              }

        )
      }
    }

    return (

昵称

{nickName}

圈外 ID

{riseId}

        {
          memberId ?

学号

{memberId}

 : null
        }
        <MarkBlock module={'打点'} func={'我的账户'} action={'点击圈外会员'} className="item"
                   onClick={() => this.handleClickGoMemberDesc()}>

圈外会员

            {memberType ? memberType : '点击加入'}

        </MarkBlock>
        <MarkBlock module={'打点'} func={'我的账户'} action={'修改手机号'} className="item item-margin"
             style={{ margin: '25px 0' }}
             onClick={() => this.goMobileCheck()}>

            {mobile ? '修改手机号' : '手机号'}

            {mobile ? {mobile} : 去绑定手机号  }

        </MarkBlock>
        {/*{renderCoupons()}*/}

    )
  }
}
<file_sep>/src/modules/home/components/live/LiveHome.tsx
import * as React from 'react'
import './LiveHome.less'
import AssetImg from '../../../../components/AssetImg'
import { splitContentWithSuffix } from '../../../../utils/helpers'
import { mark } from '../../../../utils/request'
import { Dialog } from 'react-weui'
import { alertMsg } from 'reduxutil/actions'
import requestProxy from '../../../../components/requestproxy/requestProxy'

const { Alert } = Dialog

interface LiveHomeProps {
  data: any
}

export class LiveHome extends React.Component<LiveHomeProps, any> {

  constructor () {
    super()
    this.state = {
      showAlert: false,
      dialogButtons: [
        {
          label: '知道了',
          onClick: (e) => {
            e.stopPropagation()
            this.setState({ showAlert: false })
          },
        },
        {
          label: '立即入学',
          onClick: (e) => {
            e.stopPropagation()
            mark({ module: '打点', function: '着陆页', action: '直播点击入学' })
            this.setState({ showAlert: false })
            window.location.href = '/pay/rise'
          },
        },
      ],
    }
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired,
  }

  componentWillMount () {
    this.setState({
      data: this.props.data,
    })
  }

  handleClick (visibility, linkUrl, name) {
    mark({ module: '打点', function: '着陆页', action: '点击直播' })
    if (visibility) {
      if (linkUrl) {
        window.location.href = linkUrl
      } else {
        requestProxy.alertMessage('恭喜你已预约成功！\n直播当天，我们会通过圈外同学服务号提醒你参加直播~')
        mark({ module: '打点', function: '着陆页', action: '点击预约', memo: name })
      }
    } else {
      this.setState({ showAlert: true })
    }
  }

  render () {
    const {
      name = '',
      speaker = '',
      speakerDesc = '',
      startTimeStr = '',
      thumbnail = '',
      linkUrl = '',
      visibility = false,
    } = this.state.data

    return (

 this.handleClick(visibility, linkUrl, name)}>
        {splitContentWithSuffix(name, 10)}
        主讲人：{speaker}
        {splitContentWithSuffix(speakerDesc, 15)}
        直播时间：{startTimeStr}
        <AssetImg className="thumbnail"
                  url={thumbnail}></AssetImg>
        <Alert show={this.state.showAlert}
               buttons={this.state.dialogButtons}>需要加入圈外商学院才能看哦</Alert>

    )
  }

}
<file_sep>/src/modules/forum/commons/HeadArea/HeadArea.less
@import "../../variable.less";

// HeadAreaProps
.head-area {
  width: 100%;
  height: 25px;
  background-color: @color_white;
  padding: 2px 5px;
  position: fixed;
  border-bottom: 1px solid @color_weak;
  z-index: 10;

  .head-content {
    height: 100%;
    display: inline-block;
    font-size: @font_context;
    line-height: @font_context;
  }
  .head-btn {
    display: inline-block;
    font-size: @font_index;
    line-height: @font_index;
    color: #FFF;
    background-color: #169bd5;
    position: absolute;
    right: 15px;
    top: 5px;
    padding: 3px 13px;
    border-radius: 3px;
  }

}
<file_sep>/src/components/Tutorial.tsx
import * as React from "react";
import "./Tutorial.less"
import SwipeableViews from 'react-swipeable-views';

export default class Tutorial extends React.Component<any,any> {
  constructor(props) {
    super(props);
    this.state = {
      bgList: props.bgList,//图片列表
      topList:props.topList, //图片离顶部的距离
      bottomList:props.bottomList, //图片离底部的距离
      index: 0,
      onShowEnd: props.onShowEnd || function () {
      },
      close:false,
    }
  }

  next() {
    const {index, bgList} = this.state;
    if (index >= bgList.length - 1) {
      this.state.onShowEnd();
    } else {
      this.setState({index: index + 1});
    }

  }

  onSwitching(index,type){
    const {bgList,onShowEnd,close} = this.state;
    switch(type){
      case 'end':{
        if(index===bgList.length-1 && close){
          onShowEnd();
        } else {
          this.setState({index:index,close:false});
        }
        break;
      }
      case 'move':{
        if(index>bgList.length-1){
          this.setState({close:true});
        }
        break;
      }
      default:
    }

  }

  render() {
    const {index, bgList, topList, bottomList} = this.state;

    const renderTutorialImage = (item, seq) =>{
      if(topList && topList[seq]){
        let top = topList[seq];
        return (

        )

      }else if(bottomList && bottomList[seq]){
        let bottom = bottomList[seq];
        return (

        )
      }else{
        return (

)
      }

    }

    return (
      this.props.show?

this.next()}>
        <SwipeableViews style={{width:'100%',height:'100%'}} slideStyle={{width:"100%"}} containerStyle={{width:'100%',height:'100%'}}
                        index={index} onSwitching={(index,type)=>this.onSwitching(index,type)} resistance={true}>
          {bgList.map((item, seq) => {
            return renderTutorialImage(item, seq);
          })}
        </SwipeableViews>

:null
    )
  }
}
<file_sep>/src/modules/forum/question/SubmitQuestionDetail.less
@import "../variable.less";

.question-detail-container {
  overflow: scroll;
  height: 100%;
  .question-page {
    height: 100%;
    width: 100%;
    .page-title{
      font-size: @font_title;
      text-align: center;
      color: @color_title;
      margin: 15px 0;
    }
    .tag-divide {
      padding: 0 15px;
      color: @color_title;
      height: 20px;
      line-height: 21px;
      margin-bottom: 20px;
      font-size: @font_click;
      border-bottom: 1px solid @color_weak_background;
      background-color: @color_weak_background;
    }
    .tag-list-text {
      font-size: 1.3em;
      color: @color_help;
      margin: 0 15px 20px 15px;
    }
    .tag-list{
      .tag-line{
        margin-bottom: 20px;
        .tag{
          display: inline-block;
          margin-left: ~"calc((100% - 234px)/4)";
          height: 24px;
          width: 78px;
          border: 1px solid @color_strong;
          border-radius: @border-radius;
          font-size: 1.3em;
          line-height: 24px;
          text-align: center;
          color: @color_strong;
          background-color: @color_white;
        }
        .tag-selected{
          display: inline-block;
          margin-left: ~"calc((100% - 234px)/4)";
          height: 24px;
          width: 78px;
          border: 1px solid @color_strong;
          border-radius: @border-radius;
          font-size: 1.3em;
          line-height: 24px;
          text-align: center;
          color: @color_white;
          background-color: @color_strong;
        }
      }

      border-bottom: 10px solid @color_weak_background;
    }

    .question-title{
      width: ~"calc(100% - 30px)";
      margin-left: 15px;
      margin-top: 20px;
      .length-div{
        margin-right: 10px;
        margin-top: -25px;
        z-index: 100;
        .length-tip{
          color: @color_help;
          font-size: 11px;
          text-align: right;
        }
      }
      textarea{
        border-radius: @border-radius;
        height: 74px;
        width: ~"calc(100% - 26px)";
        display: inline-block;
        resize: none;
        border: 1px solid #ccc;
        font-size: 16px;
        padding: 10px 12px;
        box-shadow:0 0 0 rgba(0,0,0,0);
        -webkit-appearance:none;
        &::-webkit-input-placeholder{
          color: @color_weak;
          line-height: @font_context;
        }
      }
    }

    .question-detail{
      width: ~"calc(100% - 30px)";
      margin-left: 15px;
      margin-top: 40px;
      .editor {
        padding-bottom: 120px;
      }
    }
  }
}
<file_sep>/src/modules/forum/commons/DialogBottom/DialogBottomBtn.tsx
import * as React from "react"
import "./DialogBottomBtn.less"

// 问题概要 底部组件
interface DialogBottomProps {
  leftContent?: string;
  leftContentFunc?: object;
  btn1Content?: string;
  btn1ContentFunc?: object;
  btn1DisableValue?: string;
  btn2Content?: string;
  btn2ContentFunc?: object;
  btn2DisableValue?: string
}
interface DialogBottomStates {
  changeLeftContent: string;
  changeBtn1Content: string;
  changeBtn2Content: string;
}
export default class DialogBottomBtn extends React.Component<DialogBottomProps, DialogBottomStates> {

  constructor() {
    super()
    this.state = { changeLeftContent: '', changeBtn1Content: '', changeBtn2Content: '' }
  }

  componentWillMount() {
    if(this.props.leftContent) {
      this.setState({ changeLeftContent: this.props.leftContent })
    }
    if(this.props.btn1Content) {
      this.setState({ changeBtn1Content: this.props.btn1Content })
    }
    if(this.props.btn2Content) {
      this.setState({ changeBtn2Content: this.props.btn2Content })
    }
  }

  componentWillReceiveProps(newProps) {
    if(newProps.btn1Content !== this.state.changeBtn1Content) {
      this.setState({ changeBtn1Content: newProps.btn1Content })
    }
    if(newProps.btn2Content !== this.state.changeBtn2Content) {
      this.setState({ changeBtn2Content: newProps.btn2Content })
    }
  }

  render() {
    const {
      leftContent, leftContentFunc = () => {
      },
      btn1DisableValue = '', btn1Content, btn1ContentFunc = () => {
      },
      btn2DisableValue = '', btn2Content, btn2ContentFunc = () => {
      }
    } = this.props;
    const { changeLeftContent, changeBtn1Content, changeBtn2Content } = this.state;

    const renderLeftContent = () => {
      if(!leftContent) return
      const changeLeftContentFunc = () => {
        let funcReturnContent = leftContentFunc()
        this.setState({ changeLeftContent: funcReturnContent ? funcReturnContent : leftContent })
      }
      return (

 changeLeftContentFunc()}>
          {changeLeftContent}

      )
    }

    const renderBtn1Kit = () => {
      const changeBtn1ContentFunc = () => {
        let funcReturnContent = btn1ContentFunc()
        this.setState({ changeBtn1Content: funcReturnContent ? funcReturnContent : btn1Content })
      }
      if(btn1Content) {
        return (
          <div className={`btn1-right-content ${changeBtn1Content === btn1DisableValue ? 'disable' : ''}`}
               onClick={() => changeBtn1ContentFunc()}>
            {changeBtn1Content}

        )
      }
    }

    const renderBtn2Kit = () => {
      const changeBtn2ContentFunc = () => {
        let funcReturnContent = btn2ContentFunc()
        this.setState({ changeBtn2Content: funcReturnContent ? funcReturnContent : btn2Content })
      }

      if(btn2Content) {
        return (
          <div className={`btn2-right-content ${changeBtn2Content === btn2DisableValue ? 'disable' : ''}`}
               onClick={() => changeBtn2ContentFunc()}>
            {changeBtn2Content}

        )
      }
    }

    return (

        {renderLeftContent()}

          {renderBtn1Kit()}
          {renderBtn2Kit()}

    )
  }
}
<file_sep>/src/modules/home/components/problem/ProblemHome.tsx
import * as React from 'react'
import './ProblemHome.less'
import AssetImg from '../../../../components/AssetImg'
import { orderProblem } from '../../async'
import { mark } from '../../../../utils/request'

interface ProblemHomeProps {
  data: any,
  subscribeFunc: any
}

export class ProblemHome extends React.Component<ProblemHomeProps, any> {

  constructor () {
    super()
    this.state = {}
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired,
  }

  componentWillMount () {
    this.setState({
      data: this.props.data,
      subscribeFunc: this.props.subscribeFunc,
    })
  }

  componentWillReceiveProps (nextProps) {
    if (JSON.stringify(nextProps) !== JSON.stringify(this.props)) {
      this.setState({
        data: nextProps.data,
        subscribeFunc: nextProps.subscribeFunc,
      })
    }
  }

  handleClickOrderProblem () {
    const { data = {}, subscribeFunc = () => {} } = this.state
    const { problemId = 1, abbreviation = '' } = data
    mark({ module: '打点', function: '查看详情', action: problemId, memo: abbreviation })
    this.context.router.push(`/rise/static/home/problem?id=${problemId}`)
  }

  render () {
    const { data = {}, subscribeFunc = () => {} } = this.state
    const {
      problemId = 1,
      abbreviation = '',
      name = '',
      hot = true,
      thumbnail = '',
    } = data

    return (

        {abbreviation}
        {name}
        {hot &&
21 天爆款课程
}

 this.handleClickOrderProblem()}>查看详情

        <AssetImg className="thumbnail" url={thumbnail}></AssetImg>

    )
  }

}
<file_sep>/src/modules/practice/common/ApplicationSubmit/ApplicationSubmit.tsx
import * as React from 'react'
import './ApplicationSubmit.less'
import EditorTopBar from '../../components/EditorTopBar/EditorTopBar'
import Editor from '../../../../components/simditor/Editor'
import { connect } from 'react-redux'
import { autoSaveApplicationDraft, loadApplicationPractice, submitApplicationPractice } from '../../application/async'
import { set, startLoad, endLoad, alertMsg } from 'reduxutil/actions'

const APPLICATION_AUTO_SAVING = 'rise_application_autosaving'

@connect(state => state)
export default class ApplicationSubmit extends React.Component {

  autoSaveTimer = null

  constructor () {
    super()
    this.state = {
      value: '',
    }
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired,
  }

  async componentDidMount () {
    const { id, planId } = this.props.location.query
    let res = await loadApplicationPractice(id, planId)
    let storageDraft = JSON.parse(window.localStorage.getItem(APPLICATION_AUTO_SAVING))
    if (storageDraft && storageDraft.id === id) {
      if (res.msg.overrideLocalStorage || res.msg.isSynchronized) {
        this.setState({
          value: res.msg.isSynchronized ? res.msg.content : res.msg.draft,
        }, () => {
          this.autoSaveApplicationDraft()
        })
      } else {
        this.setState({
          value: storageDraft.content,
        }, () => {
          this.autoSaveApplicationDraft()
        })
      }
    } else {
      this.setState({
        value: res.msg.isSynchronized ? res.msg.content : res.msg.draft,
      }, () => {
        this.autoSaveApplicationDraft()
      })
    }
  }

  componentWillUnmount () {
    clearInterval(this.autoSaveTimer)
  }

  autoSaveApplicationDraft () {
    clearInterval(this.autoSaveTimer)
    const { id, planId } = this.props.location.query
    this.autoSaveTimer = setInterval(() => {
      if (this.refs.editor) {
        let draft = this.refs.editor.getValue()
        if (draft.trim().length > 0) {
          autoSaveApplicationDraft(planId, id, draft)
        }
      }
    }, 10000)
  }

  autoSave () {
    if (this.refs.editor) {
      let value = this.refs.editor.getValue()
      if (value) {
        window.localStorage.setItem(APPLICATION_AUTO_SAVING, JSON.stringify({
          id: this.props.location.query.id, content: value,
        }))
      }
    }
  }

  clearStorage () {
    window.localStorage.removeItem(APPLICATION_AUTO_SAVING)
  }

  async handleSubmitApplicationSubmit () {
    const { dispatch } = this.props
    const value = this.refs.editor.getValue()
    if (!value || value.length === 0) {
      dispatch(alertMsg('请填写作业'))
      return
    } else {
      const { id, planId } = this.props.location.query
      let res = await submitApplicationPractice(planId, id, { answer: value })
      if (res.code === 200) {
        this.clearStorage()
        this.context.router.goBack()
      }
    }
  }

  render () {
    const { value } = this.state

    return (

        <EditorTopBar leftLabel={'取消'}
                      leftOnClick={() => {
                        this.context.router.goBack()
                      }}
                      description={'我的作业'}
                      rightLabel={'提交'}
                      rightOnClick={() => this.handleSubmitApplicationSubmit()}/>
        <Editor ref="editor"
                className="editor"
                moduleId="6"
                toolbarFloat={false}
                value={value}
                autoSave={() => this.autoSave()}
                placeholder="有灵感时马上记录在这里吧，系统会自动为你保存。完成后点上方按钮提交，就会得到点赞和专业点评哦！"/>

更喜欢电脑上提交？

登录 www.iquanwai.com/community

    )
  }

}
<file_sep>/src/modules/forum/question/Question.less
@import "../variable.less";

.question-container {
  .question-feedback {
    height: 26px;
    background-color: @color_strong;
    span {
      float: right;
      font-size: 1.3em;
      line-height: 1.3em;
      color: #FFF;
      display: inline-block;
      margin: 5px 15px;
    }
  }

  .question-page {
    overflow: scroll;
    width: 100%;
    height: ~"calc(100vh - 26px)";
    &.toolbar{
      height: ~"calc(100vh - 26px - 50px)";
    }
    .search-nav {
      margin: 10px 0 10px 15px;
      width: ~"calc(100% - 135px)";
      display: inline-block;
      border: 1px solid #ccc;
      border-radius: @border-radius;
      background-color: @color_weak_background;
      vertical-align: top;
      .search {
        width: 100%;
        &:before {
          background: url('https://static.iqycamp.com/images/fragment/search.png?imageslim') no-repeat bottom center/13px 13px;
          content: '';
          width: 13px;
          height: 13px;
          margin: 0 2px 3px;
          display: inline-block;
          vertical-align: middle;
        }
        .search-input {
          width: ~"calc(100% - 17px)";
          font-size: 1.4em;
          border-radius: 5px;
          color: @color_context;
          border: none;
          height: 25px;
          line-height: 25px;
          -webkit-appearance: none;
          background-color: @color_weak_background;
          box-shadow: 0 0 0 rgba(0, 0, 0, 0);
        }
      }
    }

    .ques-nav-btn {
      display: inline-block;
      margin: 0 0 0 19px;
      height: 47px;
      line-height: 47px;
      text-align: center;
      float: right;
      margin-right: 15px;
      .ques-btn {
        font-size: @font_index;
        color: @color_title;
      }
    }

    .ques-list {
      .simple-ques-desc {
        padding: 10px 15px;
        background-color: #fff;
        margin-bottom: 1px;
        .simple-ques-title {
          color: @color_context;
          font-size: @font_context;
          line-height: @font_context;
          overflow: hidden;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
          -moz-box-orient: vertical;
        }
      }
      .ques-desc {
        padding: 10px 15px;

        .ques-title {
          margin-left: 35px;
          color: @color_context;
          font-size: @font_context;
          line-height: @font_context;
          overflow: hidden;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
          -moz-box-orient: vertical;
        }
        .ques-content {
          margin-top: 15px;
          margin-left: 35px;
          color: @color_help;
          font-size: @font_click;
          line-height: @font_click;
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }
        .ques-answer-persons {
          margin-top: 10px;
          font-size: @font_help;
          text-align: right;
          color: @color_strong;
        }
      }
    }
  }

  .grey-banner {
    font-size: @font_click;
    color: @color_help;
    background-color: @color_weak_background;
  }

}
<file_sep>/src/modules/customer/person/SchoolFriend.less
.school-friend-container {
  background-color: #FFFFFF;
  .select-container {
    position:fixed;
    height:1.8rem;
    width:92%;
    padding:4% 1.6rem 0rem 4%;
    .select-item{
      display: inline-block;
      width:25%;
      font-size:1.3rem;
      text-align: center;
      &:after{
        display: inline-block;
        width: 1rem;
        height: 1rem;
        padding-left: 0.5rem;
        content: '';
        background: url("https://static.iqycamp.com/images/arrow_bottom.png?imageslim") no-repeat right center/  1rem 1rem;
      }
    }
  }
  .school-friend-list{
    padding:6.6rem 5% 2rem 5%;

    .school-friend-item{
      position:relative;
      height:12rem;
      box-shadow:0 3px 20px 4px rgba(222,225,240,0.58);

      img{
        position:absolute;
        top:1rem;
        left:1rem;
        width: 10rem;
        height:10rem;
      }
      .nickname{
        position:absolute;
        top: 1.2rem;
        left:12rem;
        font-size:1.4rem;
        color:#333333;
      }
      .introduction{
        position:absolute;
        top:3.6rem;
        left:12rem;
        font-size:1.2rem;
        color:#666666;
      }
      .memberId{
        position:absolute;
        top:7.9rem;
        left:12rem;
        font-size:1.2rem;
        color:#666666;
      }

    }
  }

}
<file_sep>/src/modules/customer/ProblemGallery.tsx
import * as React from 'react'
import { connect } from 'react-redux'
import './ProblemGallery.less'
import { set, startLoad, endLoad, alertMsg } from 'reduxutil/actions'
import { pget, ppost } from 'utils/request'
import { mark } from 'utils/request'
import { changeTitle } from 'utils/helpers'
import { MarkBlock } from '../../components/markblock/MarkBlock'

/**
 * 我的课程页面
 */
@connect(state => state)
export default class ProblemGallery extends React.Component<any, any> {

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  constructor(props) {
    super(props)
    this.state = {}
  }

  componentWillMount() {
    mark({ module: '打点', function: '个人中心', action: '打开我的课程页面' })
    changeTitle('我的课程')
    const { dispatch } = this.props
    dispatch(startLoad())
    pget('/rise/customer/plans').then(res => {
      dispatch(endLoad())
      if(res.code === 200) {
        this.setState(res.msg)
      } else {
        dispatch(alertMsg(res.msg))
      }
    }).catch(err => {
      dispatch(endLoad())
      dispatch(alertMsg(err + ''))
    })
  }

  goPointTip() {
    this.context.router.push({ pathname: '/rise/static/customer/point/tip' })
  }

  goPlanView(item) {
    this.context.router.push({
      pathname: '/rise/static/problem/cards',
      query: { planId: item.planId }
    })
  }

  goCertificateView(item) {
    let query = { certificateNo: item.certificateNo }
    this.context.router.push({ pathname: '/rise/static/customer/certificate', query })
  }

  render() {
    const { runningPlans = [], donePlans = [], point, riseCertificates = [], problemCollections = [] } = this.state

    const renderGalleyList = (plans) => {
      plans = plans.filter(plan => plan.learnable === true)
      return (

          { plans.map((item, index) => {
            return (
              <MarkBlock module={'打点'} func={'我的课程页面'} action={'点击课程学习'} key={index} className="item"
                         onClick={() => this.goPlanView(item)}>

                  {item.name}

                  {item.point + ' 积分'}

              </MarkBlock>
            )
          }) }

      )
    }

    const renderCertificateList = (certificates) => {
      return (

          {certificates.map((item, index) => {
              return (
                <MarkBlock module={'打点'} func={'我的课程页面'} action={'点击查看证书'} key={index} className="item"
                           onClick={() => this.goCertificateView(item)}>

                    {item.type == 1 ? '【优秀班长】' + item.month + '月训练营' : ''}
                    {item.type == 2 ? '【优秀组长】' + item.month + '月训练营' : ''}
                    {item.type == 3 ? '【优秀学员】' + item.month + '月训练营' : ''}
                    {item.type == 4 ? '【优秀团队】' + item.month + '月训练营' : ''}
                    {item.type == 5 ? '【结课证书】' + item.month + '月训练营' : ''}
                    {item.type == 6 ? '【优秀助教】' + item.month + '月训练营' : ''}

                </MarkBlock>
              )
            }) }

      )
    }

    const renderProblemCollection = (problemCollections) => {
      return (
        problemCollections.map((problem, index) => (

 this.context.router.push(`/rise/static/plan/view?id=${problem.id}&show=true`)}>

              {problem.problem}

        ))
      )
    }

    return (

        <MarkBlock module={'打点'} func={'我的课程页面'} action={'点击总积分按钮'} className="problem-galley-header"
                   onClick={() => this.goPointTip()}
                   style={{ marginBottom: '10px', borderBottom: 'none' }}>

            总积分

            {point}{'积分'}

        </MarkBlock>

          {riseCertificates.length > 0 &&

                  训练营证书

              {renderCertificateList(riseCertificates)}

          }
          {problemCollections.length > 0 &&

                  已收藏

                {renderProblemCollection(problemCollections)}

          }
          {runningPlans.length > 0 &&

                  进行中

              {renderGalleyList(runningPlans)}

          }

                已完成

            {renderGalleyList(donePlans)}

    )
  }
}
<file_sep>/src/modules/plan/async.ts
import { pget, ppost } from 'utils/request'

export function loadPlan(planId) {
  let param = {}
  if(planId) {
    param.planId = planId
  }
  return pget(`/rise/plan/load`, param)
}

export function completePlan(planId) {
  return ppost(`/rise/plan/complete${planId ? '?planId=' + planId : ''}`)
}

export function markPlan(series, planId) {
  return ppost(`/rise/plan/mark/${series}${planId ? '?planId=' + planId : ''}`)
}

export function closePlan(planId) {
  return ppost(`/rise/plan/close${planId ? '?planId=' + planId : ''}`)
}

export function checkPractice(series, planId) {
  return ppost(`/rise/plan/check/${series}${planId ? '?planId=' + planId : ''}`)
}

export function gradeProblem(problemScores, problemId) {
  return ppost(`/rise/problem/grade/${problemId}`, problemScores)
}

export function isRiseMember() {
  return pget('/rise/plan/risemember')
}

export function learnKnowledge(practicePlanId) {
  return ppost(`/rise/practice/knowledge/learn/${practicePlanId}`)
}

export function createPlan(problemId) {
  return ppost(`/rise/plan/choose/problem/${problemId}`)
}

export function queryEventList() {
  return pget('/rise/customer/event/list')
}

export function queryChapterList(planId) {
  return pget(`/rise/plan/chapter/list`, { planId: planId })
}

export function queryReport(planId) {
  return pget(`/rise/plan/improvement/report${planId ? '?planId=' + planId : ''}`)
}

export function loadRecommendations(problemId) {
  return pget(`/rise/plan/improvement/report/recommendation/${problemId}`)
}

export function loadPlanList() {
  return pget('/rise/plan/list')
}

// 章节结束获取章节精华图片
export function loadChapterCard(problemId, practicePlanId) {
  return pget(`/rise/plan/chapter/card/${problemId}/${practicePlanId}`)
}

// 章节结束返回是否弹出精华卡片
export function loadChapterCardAccess(problemId, practicePlanId) {
  return pget(`/rise/plan/chapter/card/access/${problemId}/${practicePlanId}`)
}

export function loadHasGetOperationCoupon() {
  return pget('/rise/operation/free/coupon')
}

export function createCampPlan(problemId) {
  return ppost(`/rise/plan/choose/problem/camp/${problemId}`)
}

export function hasPrivilege() {
  return pget(`/signup/check/business/school/privilege`)
}

export function loadStudyline(planId) {
  return pget(`/rise/plan/load/studyline/${planId}`)
}

export function unlockCampPlan(planId) {
  return ppost(`/rise/plan/choose/problem/camp/unlock/${planId}`)
}

/**
 * 试听课开课
 */
export function openAudition() {
  return ppost('/rise/plan/open/audition/course')
}
<file_sep>/src/routes.tsx
import * as React from 'react'

const path = require('path')
import { Route } from 'react-router'
import Base from './modules/base/Base'
import { config } from './modules/helpers/JsConfig'
import Loading from './Loading'
import Loadable from 'react-loadable'
import DailyTalk from './modules/daily/DailyTalk'
import { sa } from './utils/helpers'

import GlobalRequest from './components/globalalert/GlobalRequest'
import DiscussDistrict from './modules/practice/components/DiscussDistrict/DiscussDistrict'
import CommentSubmit from './modules/practice/common/CommentSubmit/CommentSubmit'
import ApplicationSubmit from './modules/practice/common/ApplicationSubmit/ApplicationSubmit'
import ProblemPreview from './modules/practice/preview/ProblemPreview'

const LangdingPage = Loadable({
  loader: () => import('./modules/home/LandingPage'),
  loading: Loading,
})
const LiveListPage = Loadable({
  loader: () => import('./modules/home/livelist/LiveListPage'),
  loading: Loading,
})
const ActivityListPage = Loadable({
  loader: () => import('./modules/home/activitylist/ActivityListPage'),
  loading: Loading,
})
const ProblemDesc = Loadable({
  loader: () => import('./modules/home/problemdesc/ProblemDesc'),
  loading: Loading,
})
const PlanList = Loadable({
  loader: () => import('./modules/plan/PlanList'),
  loading: Loading,
})
const Explore = Loadable({
  loader: () => import('./modules/problem/Explore'),
  loading: Loading,
})
const MoreProblem = Loadable({
  loader: () => import('./modules/problem/MoreProblem'),
  loading: Loading,
})
const BannerArticle = Loadable({
  loader: () => import('./modules/problem/BannerArticle'),
  loading: Loading,
})
const ProblemExtension = Loadable({
  loader: () => import('./modules/practice/extension/ProblemExtension'),
  loading: Loading,
})
const CardsCollection = Loadable({
  loader: () => import('./modules/problem/CardsCollection'),
  loading: Loading,
})
const ProblemIntroduction = Loadable({
  loader: () => import('./modules/practice/introduction/ProblemIntroduction'),
  loading: Loading,
})
const StudyLine = Loadable({
  loader: () => import('./modules/plan/StudyLine'),
  loading: Loading,
})
const ImprovementReport = Loadable({
  loader: () => import('./modules/plan/ImprovementReport'),
  loading: Loading,
})
const WarmUpNew = Loadable({
  loader: () => import('./modules/practice/warmup/Warmup'),
  loading: Loading,
})
const AnalysisNew = Loadable({
  loader: () => import('./modules/practice/warmup/AnalysisNew'),
  loading: Loading,
})
const Application = Loadable({
  loader: () => import('./modules/practice/application/Application'),
  loading: Loading,
})
const Challenge = Loadable({
  loader: () => import('./modules/practice/challenge/Challenge'),
  loading: Loading,
})
const ApplicationComment = Loadable({
  loader: () => import('./modules/practice/application/Comment'),
  loading: Loading,
})
const Subject = Loadable({
  loader: () => import('./modules/practice/subject/Subject'),
  loading: Loading,
})
const KnowledgeViewer = Loadable({
  loader: () => import('./modules/practice/knowledge/KnowledgeViewer'),
  loading: Loading,
})
const KnowledgeShare = Loadable({
  loader: () => import('./modules/practice/knowledge/KnowledgeShare'),
  loading: Loading,
})
const KnowledgeReview = Loadable({
  loader: () => import('./modules/practice/knowledge/KnowledgeReview'),
  loading: Loading,
})
const ProblemPreview = Loadable({
  loader: () => import('./modules/practice/preview/ProblemPreview'),
  loading: Loading,
})
const ReplyDiscussMessage = Loadable({
  loader: () => import('./modules/message/ReplyWarmupDiscussMessage'),
  loading: Loading,
})
const ReplyApplicationComment = Loadable({
  loader: () => import('./modules/message/ReplyApplicationComment'),
  loading: Loading,
})
const ReplyKnowledgeDiscussMessage = Loadable({
  loader: () => import('./modules/message/ReplyKnowledgeDiscussMessage'),
  loading: Loading,
})
const ReplyCommentMessage = Loadable({
  loader: () => import('./modules/message/ReplyCommentMessage'),
  loading: Loading,
})
const SendCard = Loadable({
  loader: () => import('./modules/customer/card/experience/SendCard'),
  loading: Loading,
})
const Customer = Loadable({
  loader: () => import('./modules/customer/Customer'),
  loading: Loading,
})
const Personal = Loadable({
  loader: () => import('./modules/customer/Personal'),
  loading: Loading,
})
const PersonalModify = Loadable({
  loader: () => import('./modules/customer/account/PersonalModify'),
  loading: Loading,
})
const Coupon = Loadable({
  loader: () => import('./modules/customer/Coupon'),
  loading: Loading,
})
const Profile = Loadable({
  loader: () => import('./modules/customer/Profile'),
  loading: Loading,
})

const NewProfile = Loadable({
  loader: () => import('./modules/customer/NewProfile'),
  loading: Loading,
})

const Account = Loadable({
  loader: () => import('./modules/customer/account/Account'),
  loading: Loading,
})
const NickName = Loadable({
  loader: () => import('./modules/customer/account/components/NickName'),
  loading: Loading,
})
const HeadImage = Loadable({
  loader: () => import('./modules/customer/account/components/HeadImage'),
  loading: Loading,
})
const PointTip = Loadable({
  loader: () => import('./modules/customer/PointTip'),
  loading: Loading,
})
const ProblemGallery = Loadable({
  loader: () => import('./modules/customer/ProblemGallery'),
  loading: Loading,
})
const RiseMember = Loadable({
  loader: () => import('./modules/customer/RiseMember'),
  loading: Loading,
})
const FeedBack = Loadable({
  loader: () => import('./modules/customer/faq/Feedback'),
  loading: Loading,
})
const Faq = Loadable({
  loader: () => import('./modules/customer/faq/Faq'),
  loading: Loading,
})
const UserProtocol = Loadable({
  loader: () => import('./modules/customer/UserProtocol'),
  loading: Loading,
})
const MobileBind = Loadable({
  loader: () => import('./modules/customer/MobileBind'),
  loading: Loading,
})
const CertificateProfile = Loadable({
  loader: () => import('./modules/customer/certificate/Personal'),
  loading: Loading,
})
const Certificate = Loadable({
  loader: () => import('./modules/customer/certificate/Main'),
  loading: Loading,
})
const MineCard = Loadable({
  loader: () => import('./modules/customer/card/MineCard'),
  loading: Loading,
})
const MessageCenter = Loadable({
  loader: () => import('./modules/message/MessageCenter'),
  loading: Loading,
})
const EventWall = Loadable({
  loader: () => import('./modules/plan/EventWall'),
  loading: Loading,
})
const ForumBase = Loadable({
  loader: () => import('./modules/forum/ForumBase'),
  loading: Loading,
})
const QuestionAnswer = Loadable({
  loader: () => import('./modules/forum/question/QuestionAnswer'),
  loading: Loading,
})
const AnswerComment = Loadable({
  loader: () => import('./modules/forum/question/AnswerComment'),
  loading: Loading,
})
const Question = Loadable({
  loader: () => import('./modules/forum/question/Question'),
  loading: Loading,
})
const SubmitQuestionInit = Loadable({
  loader: () => import('./modules/forum/question/SubmitQuestionInit'),
  loading: Loading,
})
const SubmitQuestionDetail = Loadable({
  loader: () => import('./modules/forum/question/SubmitQuestionDetail'),
  loading: Loading,
})
const InterlocutionQuestion = Loadable({
  loader: () => import('./modules/interlocution/InterlocutionQuestion'),
  loading: Loading,
})
const InterlocutionQuestionSubmit = Loadable({
  loader: () => import('./modules/interlocution/InterlocutionQuestionSubmit'),
  loading: Loading,
})
const InterlocutionQuanAnswer = Loadable({
  loader: () => import('./modules/interlocution/InterlocutionQuanAnswer'),
  loading: Loading,
})
const ScheduleNotice = Loadable({
  loader: () => import('./modules/schedule/ScheduleNotice'),
  loading: Loading,
})
const ScheduleChoice = Loadable({
  loader: () => import('./modules/schedule/ScheduleChoice'),
  loading: Loading,
})
const OverView = Loadable({
  loader: () => import('./modules/schedule/overview/OverView'),
  loading: Loading,
})
const SchedulePlan = Loadable({
  loader: () => import('./modules/schedule/plan/SchedulePlan'),
  loading: Loading,
})
const ProblemNoPublish = Loadable({
  loader: () => import('./modules/schedule/nopublish/ProblemNoPublish'),
  loading: Loading,
})
const Transfer = Loadable({
  loader: () => import('./modules/schedule/overview/components/Transfer'),
  loading: Loading,
})
const AnnualSummary = Loadable({
  loader: () => import('./modules/promotion/annualsummary/AnnualSummary'),
  loading: Loading,
})
const SelfEvaluate = Loadable({
  loader: () => import('./modules/evaluation/self/SelfEvaluate'),
  loading: Loading,
})
const OtherEvaluate = Loadable({
  loader: () => import('./modules/evaluation/other/OtherEvaluate'),
  loading: Loading,
})
const ProblemCardList = Loadable({
  loader: () => import('./modules/customer/knowledgecards/ProblemCardList'),
  loading: Loading,
})
const StudyList = Loadable({
  loader: () => import('./modules/customer/person/StudyList'),
  loading: Loading,
})

const ShowCertificate = Loadable({
  loader: () => import('./modules/customer/person/ShowCertificate'),
  loading: Loading,
})

const RichTextView = Loadable({
  loader: () => import('./modules/other/richtext/RichTextView'),
  loading: Loading,
})

const routes = (
  <Route>
    <Route path="/rise/static" component={Base} onChange={(before, after) => {
      config([ 'chooseWXPay' ])
      if(after.location.state && after.location.state.pageScrollY) {
        setTimeout(() => {
          window.scrollTo(0, after.location.state.pageScrollY)
        }, 1000)
      } else {
        window.scrollTo(0, 0)
      }
      sa.quick('autoTrackSinglePage');
    }}>
      <Route path={'demo'} component={DiscussDistrict}/>
      <Route path="home" component={LangdingPage}/>
      <Route path="home/lives" component={LiveListPage}/>
      <Route path="home/activities" component={ActivityListPage}/>
      <Route path="home/problem" component={ProblemDesc}/>

      <Route path="rise" component={SchedulePlan}/>
      <Route path="camp" component={SchedulePlan}/>
      <Route path="learn" component={SchedulePlan}/>
      <Route path="plan/main" component={SchedulePlan}/>

      <Route path="daily/talk" component={DailyTalk}/>

      <Route path="problem/explore" component={Explore}/>
      <Route path="problem/more" component={MoreProblem}/>
      <Route path="problem/package" component={BannerArticle}/>
      <Route path="problem/extension" component={ProblemExtension}/>
      <Route path="problem/cards" component={CardsCollection}/>

      <Route path="plan/view" component={ProblemIntroduction}/>
      <Route path="plan/study" component={StudyLine}/>
      <Route path="plan/report" component={ImprovementReport}/>

      <Route path="practice/warmup" component={WarmUpNew}/>
      <Route path="practice/warmup/new/analysis" component={AnalysisNew}/>
      <Route path="practice/application" component={Application}/>
      <Route path="practice/challenge" component={Challenge}/>
      <Route path="practice/application/comment" component={ApplicationComment}/>
      <Route path="practice/subject/comment" component={Subject}/>
      <Route path="practice/knowledge" component={KnowledgeViewer}/>
      <Route path="practice/preview" component={ProblemPreview}/>
      <Route path="guest/practice/knowledge/view" component={KnowledgeShare}/>
      <Route path="practice/knowledge/review" component={KnowledgeReview}/>
      <Route path="practice/submit/comment" component={CommentSubmit}/>
      <Route path="practice/submit/application" component={ApplicationSubmit}/>

      <Route path="message/warmup/reply" component={ReplyDiscussMessage}/>
      <Route path="message/subject/reply" component={Subject}/>
      <Route path="message/application/reply" component={ReplyApplicationComment}/>
      <Route path="message/knowledge/reply" component={ReplyKnowledgeDiscussMessage}/>
      <Route path="message/comment/reply" component={ReplyCommentMessage}/>
      <Route path="guest/card/send" component={SendCard}/>
      <Route path="feedback" component={FeedBack}/>
      <Route path="faq" component={Faq}/>
      <Route path="userprotocol" component={UserProtocol}/>

      <Route path="article" component={RichTextView}/>

      <Route path="coupon" component={Coupon}/>
      <Route path="knowledge/card/list" component={ProblemCardList}/>
      <Route path="person/study/list" component={StudyList}/>
      <Route path="person/certificate" component={ShowCertificate}/>
      <Route path="message/center" component={MessageCenter}/>

      <Route path="customer">
        <Route path="personal" component={Personal}/>
        <Route path="personal/modify" component={PersonalModify}/>
        <Route path="profile" component={Profile}/>
        <Route path="account" component={Account}/>
        <Route path="modify/nickname" component={NickName}/>
        <Route path="modify/headImg" component={HeadImage}/>
        <Route path="point/tip" component={PointTip}/>
        <Route path="problem" component={ProblemGallery}/>
        <Route path="member" component={RiseMember}/>
        <Route path="mobile/check" component={MobileBind}/>
        <Route path="certificate/profile" component={CertificateProfile}/>
        <Route path="certificate" component={Certificate}/>
        <Route path="prize/card/list" component={MineCard}/>
        <Route path="new/profile" component={NewProfile}/>
      </Route>

      <Route path="event/wall" component={EventWall}/>

      <Route component={ForumBase}>
        <Route path="/rise/static/message/question/answer" component={QuestionAnswer}/>
        <Route path="/rise/static/message/answer/comment" component={AnswerComment}/>
        <Route path="/forum/static/question" component={Question}/>
        <Route path="/forum/static/answer" component={QuestionAnswer}/>
        <Route path="/forum/static/answer/comment" component={AnswerComment}/>
        <Route path="/forum/static/question/init" component={SubmitQuestionInit}/>
        <Route path="/forum/static/question/detail" component={SubmitQuestionDetail}/>
      </Route>

      <Route path="guest/inter/questions" component={InterlocutionQuestion}/>
      <Route path="inter/question/submit" component={InterlocutionQuestionSubmit}/>
      <Route path="guest/inter/quan/answer" component={InterlocutionQuanAnswer}/>

      <Route path="course/schedule/start" component={ScheduleNotice}/>
      <Route path="course/schedule/choice" component={ScheduleChoice}/>
      <Route path="course/schedule/overview" component={OverView}/>
      <Route path="course/schedule/plan" component={SchedulePlan}/>
      <Route path="course/schedule/nopublish" component={ProblemNoPublish}/>

      <Route path="transfer" component={Transfer}/>
      <Route path="guest/annual/summary" component={AnnualSummary}/>
      <Route path="/rise/static/guest/value/evaluation/self" component={SelfEvaluate}/>
      <Route path="/rise/static/guest/value/evaluation/other" component={OtherEvaluate}/>
    </Route>
  </Route>
)

export default routes
<file_sep>/src/modules/forum/commons/ForumComponent.tsx
import HeadArea from "./HeadArea/HeadArea";
import DialogHead from "./DialogHead/DialogHead";
import DialogBottomBtn from "./DialogBottom/DialogBottomBtn";
import DialogBottomIcon from "./DialogBottom/DialogBottomIcon";
import SimpleQuestion from "./SimpleQuestion/SimpleQuestion";
import ForumButton from "./ForumButton/ForumButton";
import PullSlideTip from "./PullSlideTip/PullSlideTip";

export {
  HeadArea,
  DialogHead,
  DialogBottomBtn,
  DialogBottomIcon,
  SimpleQuestion,
  ForumButton,
};

<file_sep>/src/modules/forum/question/AnswerComment.less
@import "../variable";

.ans-comment-container {
  height: ~"calc(100% + 1px)";
  overflow: scroll;

  .ans-comment-page {
    padding: 15px;

    .ans-desc {
      border-bottom: 1px solid @color_weak_background;
      margin: 0 -15px;
      padding: 0 15px 30px;

      .ans-content {
        color: @color_title;
        font-size: 1.4em;
        line-height: 1.4em;
        margin-top: 15px;
        margin-bottom: 20px;
      }
    }

    .ans-comment-list {

      .ans-comment-desc {
        border-bottom: 1px solid @color_weak_background;
        margin: 0 -15px;
        padding: 15px;

        .dialog-right-content  {
          border: 0;
          color: @color_strong;
        }

        .ans-comment-content {
          font-size: 1.4em;
          line-height: 1.4em;
          color: @color_title;
          margin-top: 8px;
          padding-left: 35.5px;
          white-space: pre-wrap;
          word-wrap: break-word;
        }

        .ans-comment-replied {
          font-size: @font_index;
          line-height: @font_index;
          color: @color_help;
          margin-top: 8px;
          padding-left: 35.5px;
          white-space: pre-wrap;
          word-wrap: break-word;
        }
      }
    }
  }
}
<file_sep>/src/modules/customer/person/SchoolFriend.tsx
import * as React from 'react'
import { connect } from 'react-redux'
import './SchoolFriend.less'
import { alertMsg } from 'reduxutil/actions'
import { loadAllElites } from './async'

/**
 * 校友录（只有核心项目和商业进阶看到，展示核心项目和商业进阶）
 */
@connect(state => state)
export default class SchoolFriend extends React.Component<any, any> {

  constructor() {
    super()
    this.state = {
      clickedItem: 0,
      elites: [],

    }
  }

  async componentWillMount() {
    const { dispatch } = this.props
    let res = await loadAllElites()
    console.log(res)
    if(res.code === 200) {
      this.setState({
        elites: res.msg
      })
    } else {
      dispatch(alertMsg(res.msg))
    }

  }

  goAll() {
    this.setState({
      clickItem: 0
    })
  }

  goCity() {

    this.setState({
      clickItem: 1
    })
  }

  goIndustry() {
    this.setState({
      clickItem: 2
    })
  }

  goWorkYear() {
    this.setState({
      clickItem: 3
    })
  }

  render() {

    const { clickItem = 0, elites = [] } = this.state

    const renderSelect = () => {
      return (

 this.goAll()}
               style={{ color: clickItem === 0 ? '#333333' : '#666666' }}>
            显示全部

 this.goCity()}
               style={{ color: clickItem === 1 ? '#333333' : '#666666' }}>
            所在城市

 this.goIndustry()}
               style={{ color: clickItem === 2 ? '#333333' : '#666666' }}>
            所在行业

 this.goWorkYear()}
               style={{ color: clickItem === 3 ? '#333333' : '#666666' }}>
            工作年限

      )
    }

    const renderList = () => {
      return (

          {
            elites.forEach((item)=>{

                  {item.nickName}

                  {item.province} | {item.industry} | 工作年限：{item.workLife}

                  学号：{item.memberId}

            })
          }

      )
    }

    return (

        {renderSelect()}
        {renderList()}
        {/*{renderChoice()}*/}

    )
  }

}
<file_sep>/mock/forum/router.js
var Router = require("express").Router;
var router = new Router();

router.get("/forum/question/load/list", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        "msg": {
          "list": [ {
            "id": 7,
            "topic": "测22222试",
            "description": "描述up2222date",
            "profileId": null,
            "followCount": 2,
            "openCount": 118,
            "answerCount": 4,
            "weight": 124,
            "addTime": 1497957893000,
            "answerTips": "查看三十文、薛定谔的猫等2人的回答",
            "addTimeStr": "2017-06-20",
            "authorUserName": "薛定谔的猫",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/Q3auHgzwzM7wkhob9zgicD3IJxG1tLVSSe9qdzR1qUGXz6BwPv73sr67iaTEibcA1sNic3Roib4DgXCVG4IWe0zPAKJnlo5r4NibezssS6naic6dkM/0",
            "follow": true,
            "mine": true,
            "answerList": null,
            "questionTagList": null
          }, {
            "id": 2,
            "topic": "如何成为没有一点用的人",
            "description": "你不能巴拉巴拉巴拉巴拉巴拉这么做。。。你不能巴拉巴拉巴拉巴拉巴拉这么做。。。你要巴拉巴拉巴拉巴拉巴拉这么做。。。",
            "profileId": null,
            "followCount": 0,
            "openCount": 37,
            "answerCount": 2,
            "weight": 37,
            "addTime": 1497926416000,
            "answerTips": "查看薛定谔的猫、三十文等2人的回答",
            "addTimeStr": "2017-06-20",
            "authorUserName": "?雷立风行",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/PiajxSqBRaEI6Ett47SE95j62d8rLN0FIcgAFdUXpBqosmXDpZzDOQ8TMNX92DP3icZqEXQtcoicnezvvFINTIlaw/0",
            "follow": false,
            "mine": false,
            "answerList": null,
            "questionTagList": null
          }, {
            "id": 1,
            "topic": "如何成为有用的人",
            "description": "你要巴拉巴拉巴拉巴拉巴拉这么做。。。你要巴拉巴拉巴拉巴拉巴拉这么做。。。你要巴拉巴拉巴拉巴拉巴拉这么做。。。",
            "profileId": null,
            "followCount": 2,
            "openCount": 9,
            "answerCount": 1,
            "weight": 15,
            "addTime": 1497926353000,
            "answerTips": "查看三十文的回答",
            "addTimeStr": "2017-06-20",
            "authorUserName": "缪能达",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/ajNVdqHZLLDAbh37MBYIQNJfT256gPjftKiaQYu3QFGqgEGS3UBGcJAZMz784z5Fo7iapkUsMMJMicaASlsOqM7ibm6iaz4vO1VzAkibNrjrwqKJ0/0",
            "follow": true,
            "mine": false,
            "answerList": null,
            "questionTagList": null
          }, {
            "id": 6,
            "topic": "这个是标题",
            "description": "这个是内容",
            "profileId": null,
            "followCount": 22,
            "openCount": 11,
            "answerCount": 2333,
            "weight": 9,
            "addTime": 1497953740000,
            "answerTips": "成为第一个回答者",
            "addTimeStr": "2017-06-20",
            "authorUserName": "薛定谔的猫",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/Q3auHgzwzM7wkhob9zgicD3IJxG1tLVSSe9qdzR1qUGXz6BwPv73sr67iaTEibcA1sNic3Roib4DgXCVG4IWe0zPAKJnlo5r4NibezssS6naic6dkM/0",
            "follow": false,
            "mine": true,
            "answerList": null,
            "questionTagList": null
          }, {
            "id": 4,
            "topic": "思维问题",
            "description": "思维问题描述",
            "profileId": null,
            "followCount": 1,
            "openCount": 3,
            "answerCount": 0,
            "weight": 6,
            "addTime": 1497944430000,
            "answerTips": "成为第一个回答者",
            "addTimeStr": "2017-06-20",
            "authorUserName": "风之伤",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/Q3auHgzwzM4j579r72ltlZK0uBEz3klv57pOrqolSjZONIIlyffo4ib5p7sneIH4MgXyCKzKOKBiaCTkQUyu15XKiaeSppaJ0U3j1OBLIOrxrk/0",
            "follow": false,
            "mine": false,
            "answerList": null,
            "questionTagList": null
          } ], "end": true
        }, "code": 200
      }
    ), Math.random() * 1500);
});

router.get("/forum/question/load/*", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        "msg": {
          "id": 33,
          "topic": "动不动就想看手机的坏毛病怎么破？",
          "description": "自己很难注意力集中下来，做事情效率不高。举个例子比如正在看网易公开课的视频，可仍然手欠忍不住就看手机，看微信等？除了把手机放在自己视线看不到的地方，还有别的办法吗？",
          "profileId": null,
          "followCount": 4,
          "openCount": 138,
          "answerCount": 5,
          "weight": 150,
          "addTime": 1503463900000,
          "lastModifiedTime": 1503463900000,
          "answerTips": null,
          "addTimeStr": "2017-08-23",
          "authorUserName": "随心",
          "authorHeadPic": "https://wx.qlogo.cn/mmopen/pEmwvrI2S6adoic3GoppNoA3iaicRY7xCYtNtLhyNt1HSQpeYyAU69V3SW4V4nsM2JqHNWAfeiaDniaFrkDupjMSoDlgd7VAibQVhD/0",
          "follow": false,
          "mine": false,
          "answered": false,
          "answerList": [ {
            "id": 87,
            "answer": "我来推荐两个我在用的方法~

1）培养习惯。

自己之前也深深觉得自己效率不高，而想做的事情又太多，非常苦恼。

后来学习了《时间管理》那门课程，讲到要记录每天的时间流水，就是按照5分钟或10分钟这样一种时间粒度，去记录你从早到晚都做了什么，用了多少时间。

然后去分析一下哪些时间是你的常态时间，比如通勤，吃饭，睡觉；哪些是动态时间，比如一些突发事件或变化；还有黄金时间，是自己可以集中注意力、不被打扰的、可以深度解决问题的时间；还有碎片时间，比如等饭。了解自己的时间习惯后，就可以把事情按照关键度和可行性，放进不同的时间段里去。

紧接着，就尽量按照这个详尽的计划去执行。慢慢培养成自己的习惯，这样就能避开那些习惯了。

2）上面那个是长期培养的过程。下面介绍一个工具。所谓“工欲善其事必先利其器”嘛，我们可以多利用工具帮我们达成目标。

推荐一个工具。番茄工作法。它把时间切分成25分钟，然后是5分钟休息。那25分钟会设置成倒计时，显示在屏幕上。每当拿起手机，看着时间滴滴答答地走，会促使自己把手机放下，继续工作。每完成一个番茄，自己的成就感和对自我的认同感都会增强。

这两个方法对我来说比较有用。希望你也能找到适合自己的方法哦

",
            "questionId": 33,
            "profileId": null,
            "approvalCount": 3,
            "publishTime": 1503484299000,
            "lastModifiedTime": 1503484299000,
            "publishTimeStr": "2017-08-23",
            "authorUserName": "Claudia\uD83C\uDF88孙雪",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/wbKdib81ny6icIK0BaTrZAQHEGlNicG6ucZIjY65zAdPsmz39B6Cc3Eib47zwsibAicb<KEY>",
            "approval": false,
            "comments": null,
            "mine": false,
            "topic": null,
            "commentCount": 1
          }, {
            "id": 83,
            "answer": "看到这个问题不禁会心一笑，很想说这个真的不只你一个人啊，现在大家都被手机绑架得很厉害。我想大约是因为人性本身是懒惰的吧～～能做到不看手机的，大约是两种人，一种是工作很忙碌，有很多重要且紧急的事情要做，一坐下就被各种邮件电话追杀忙到飞起，肯定是没有空来刷手机了；另外一种就是内心有明确的目标，动力强劲，所以可以突破惰性的吸引力（请脑补飞机起飞的场景～）

所以我觉得吧，我们要对抗的不是手机（也无法对抗，天性使然）。关键我们要想清楚，不看手机的时间，我们想干嘛。而我们想做的事，是想达成什么样的目标，对我们有多重要，能产生多大的动力来驱动我们改变自己。有了明确的目标和行动计划，然后培养习惯去执行，这样才能真正做到。具体怎么做，内容详见我们的课程《如何改变自己》，有非常细致的阐述和方法。

其实把手机放在我们看不见的地方，就是一个很好的方法，课程中《调整环境》一节会讲到。我打算开始采用。因为最近正好也在思考这个问题，想要提高自己的工作效率，打算试行一段时间看看效果:)
\n",
            "questionId": 33,
            "profileId": null,
            "approvalCount": 2,
            "publishTime": 1503475258000,
            "lastModifiedTime": 1503475258000,
            "publishTimeStr": "2017-08-23",
            "authorUserName": "Cindy.C",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/PiajxSqBRaEJkkL8Ou2K0edGIQdDlb7icTgXnpsHT9DCGEn1hIq5S4Gz3X4O0bpgfzlDf3iaV4O9ePniaNuaymibKgA/0",
            "approval": false,
            "comments": null,
            "mine": false,
            "topic": null,
            "commentCount": 2
          }, {
            "id": 84,
            "answer": "《如何改变自何己》统计记录，可以辅助控制一下。",
            "questionId": 33,
            "profileId": null,
            "approvalCount": 1,
            "publishTime": 1503475725000,
            "lastModifiedTime": 1503475725000,
            "publishTimeStr": "2017-08-23",
            "authorUserName": "chillymoon",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/hglMZuicA3iauTIlciaKXMtjEqyelLu4BN7V24o7T3l5jOqibhJghKyRU3xqZYX2F3k4cOJ589yIxCBibLUooiaPP1zb7XqCqtUxVS/0",
            "approval": false,
            "comments": null,
            "mine": false,
            "topic": null,
            "commentCount": 1
          }, {
            "id": 100,
            "answer": "喝酒不开车、开车不喝酒
换到手机工作学习中，这句话改成“学习工作不玩手机、玩手机不学习工作”。

当然这不是绝对的哈，最好将学习工作的时间缩短，比如半小时到1小时内不看手机。

看手机学【圈外同学】课程，看文章也是学习

所以主要是自己给自己一个自愿遵守的原则，从小处做起，要求自己10小时不玩手机太夸张。

我有个小体验，就是认识到自己长期一心二用的后果，如果可以承担，为何不可？但如果自己认识到一心二用的后果，写下来，比如：一心二用我根本不能够将眼前的工作学习做到80分，最多60分，60分甚至低于60分多了，我这个人也是给人60分的感觉；学习时候看手机，思考不深入老被打断，学了个假知识而已；看手机，什么时候看都行，再说了看了别人的生活能够给我们留下什么？而且看我之后，要知道我们脑袋就像电脑内存一样，会留下很多浏览垃圾占用我们的内存，电脑有一键缓存功能，脑袋里可不是一键这么简单，看完你可能还会潜意识中思考，做梦梦见....这里花的时间可不好计算啦。

所以建议1）认识到自己任何行为的后果，愿意承担；2）如不愿意承担，那就去改变，善于理性、借助感性、制作情境。受益于【圈外同学】中的《如何改变自己》课程。都说人生在于自律，可是纯依靠自制力的自律太流氓，借助课程总结出来的方法，让自己成为一个更自律的人吧，自“践”则无敌！
",
            "questionId": 33,
            "profileId": null,
            "approvalCount": 0,
            "publishTime": 1503550167000,
            "lastModifiedTime": 1503550167000,
            "publishTimeStr": "2017-08-24",
            "authorUserName": "阿一Kelly",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/ib7IQ03GF98XjoaHgicBwkEgJibkeibgt642PsCibSvvByVXQzR6OeyFcmFQoxzOSd819hD5YCXLGjqbOQ5BdsgpdyXiaoo39r4ib9r/0",
            "approval": false,
            "comments": null,
            "mine": false,
            "topic": null,
            "commentCount": 0
          }, {
            "id": 94,
            "answer": "换成诺基亚直板儿用三天，根治。",
            "questionId": 33,
            "profileId": null,
            "approvalCount": 0,
            "publishTime": 1503534890000,
            "lastModifiedTime": 1503534890000,
            "publishTimeStr": "2017-08-24",
            "authorUserName": "<PASSWORD>",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/ib7IQ03GF98XjoaHgicBwkEp4lLJAmZveFPCPMF0324mhxibm0WEicnWvnoaXc7uhEL5GLG5rsKcfJm33rRF1T9tKSUwLzRMPuWo/0",
            "approval": false,
            "comments": null,
            "mine": false,
            "topic": null,
            "commentCount": 1
          } ],
          "forumTags": [ { "id": 7, "name": "自我管理", "del": false } ]
        }, "code": 200
      }
    ), Math.random() * 1500)
});

router.post("/forum/question/search", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        "msg": {
          "list": [ {
            "id": 7,
            "topic": "解锁的垃圾袋垃圾",
            "description": "描述up2222date",
            "profileId": null,
            "followCount": 2,
            "openCount": 118,
            "answerCount": 4,
            "weight": 124,
            "addTime": 1497957893000,
            "answerTips": "查看三十文、薛定谔的猫等2人的回答",
            "addTimeStr": "2017-06-20",
            "authorUserName": "薛定谔的猫",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/Q3auHgzwzM7wkhob9zgicD3IJxG1tLVSSe9qdzR1qUGXz6BwPv73sr67iaTEibcA1sNic3Roib4DgXCVG4IWe0zPAKJnlo5r4NibezssS6naic6dkM/0",
            "follow": true,
            "mine": true,
            "answerList": null,
            "questionTagList": null
          }, {
            "id": 2,
            "topic": "cm.cxmz.cm.11",
            "description": "你不能巴拉巴拉巴拉巴拉巴拉这么做。。。你不能巴拉巴拉巴拉巴拉巴拉这么做。。。你要巴拉巴拉巴拉巴拉巴拉这么做。。。",
            "profileId": null,
            "followCount": 0,
            "openCount": 37,
            "answerCount": 2,
            "weight": 37,
            "addTime": 1497926416000,
            "answerTips": "查看薛定谔的猫、三十文等2人的回答",
            "addTimeStr": "2017-06-20",
            "authorUserName": "?雷立风行",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/PiajxSqBRaEI6Ett47SE95j62d8rLN0FIcgAFdUXpBqosmXDpZzDOQ8TMNX92DP3icZqEXQtcoicnezvvFINTIlaw/0",
            "follow": false,
            "mine": false,
            "answerList": null,
            "questionTagList": null
          }, {
            "id": 1,
            "topic": "如何成为有用的人",
            "description": "你要巴拉巴拉巴拉巴拉巴拉这么做。。。你要巴拉巴拉巴拉巴拉巴拉这么做。。。你要巴拉巴拉巴拉巴拉巴拉这么做。。。",
            "profileId": null,
            "followCount": 2,
            "openCount": 9,
            "answerCount": 1,
            "weight": 15,
            "addTime": 1497926353000,
            "answerTips": "查看三十文的回答",
            "addTimeStr": "2017-06-20",
            "authorUserName": "缪能达",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/ajNVdqHZLLDAbh37MBYIQNJfT256gPjftKiaQYu3QFGqgEGS3UBGcJAZMz784z5Fo7iapkUsMMJMicaASlsOqM7ibm6iaz4vO1VzAkibNrjrwqKJ0/0",
            "follow": true,
            "mine": false,
            "answerList": null,
            "questionTagList": null
          }, {
            "id": 6,
            "topic": "这个是标题",
            "description": "这个是内容",
            "profileId": null,
            "followCount": 22,
            "openCount": 11,
            "answerCount": 2333,
            "weight": 9,
            "addTime": 1497953740000,
            "answerTips": "成为第一个回答者",
            "addTimeStr": "2017-06-20",
            "authorUserName": "薛定谔的猫",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/Q3auHgzwzM7wkhob9zgicD3IJxG1tLVSSe9qdzR1qUGXz6BwPv73sr67iaTEibcA1sNic3Roib4DgXCVG4IWe0zPAKJnlo5r4NibezssS6naic6dkM/0",
            "follow": false,
            "mine": true,
            "answerList": null,
            "questionTagList": null
          }, {
            "id": 4,
            "topic": "思维问题",
            "description": "思维问题描述",
            "profileId": null,
            "followCount": 1,
            "openCount": 3,
            "answerCount": 0,
            "weight": 6,
            "addTime": 1497944430000,
            "answerTips": "成为第一个回答者",
            "addTimeStr": "2017-06-20",
            "authorUserName": "风之伤",
            "authorHeadPic": "https://wx.qlogo.cn/mmopen/Q3auHgzwzM4j579r72ltlZK0uBEz3klv57pOrqolSjZONIIlyffo4ib5p7sneIH4MgXyCKzKOKBiaCTkQUyu15XKiaeSppaJ0U3j1OBLIOrxrk/0",
            "follow": false,
            "mine": false,
            "answerList": null,
            "questionTagList": null
          } ], "end": true
        }, "code": 200
      }
    ), Math.random() * 1500);
});

router.get("/forum/question/tag/load", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        "msg": [ { "id": "1", "name": "职场" }, { "id": "2", "name": "思维" }, { "id": "3", "name": "练习" }, {
          "id": "4",
          "name": "职场2"
        }, { "id": "5", "name": "思维2" } ], "code": 200
      }
    ), Math.random() * 1500);
});

router.post("/forum/question/submit", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        msg: "ok",
        code: 200
      }
    ), Math.random() * 1500);
});

router.post("/forum/question/follow/*", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        msg: "ok",
        code: 200
      }
    ), Math.random() * 1500);
});

router.post("/forum/question/follow/cancel/*", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        msg: "ok",
        code: 200
      }
    ), Math.random() * 1500);
});

router.post("/forum/answer/approve/*", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        msg: "ok",
        code: 200
      }
    ), Math.random() * 1500);
});
router.post("forum/answer/approve/cancel/*", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        msg: "ok",
        code: 200
      }
    ), Math.random() * 1500);
});

router.post("/forum/answer/delete/comment/*", (req, res) => {
  setTimeout(() =>
    res.status(200).json(
      {
        msg: "ok",
        code: 200
      }
    ), Math.random() * 1500);
});

router.post("/forum/answer/comment", (req, res) => {
  setTimeout(() => {
    res.status(200).json(
      {
        "msg": {
          "id": 0,
          "answerId": 3,
          "commentProfileId": 16443,
          "comment": "fff3飞飞飞飞333fff",
          "del": false,
          "repliedId": 3,
          "repliedProfileId": 30,
          "repliedDel": false,
          "addTime": null,
          "publishTimeStr": "2017-06-21",
          "authorUserName": "风之伤",
          "authorHeadPic": "https://www.iqycamp.com/images/default_avatar.png",
          "repliedComment": "11111",
          "repliedName": "三十文",
          "mine": true
        },
        "code": 200
      }
    )
  }, Math.random() * 1500)
})

router.get("/forum/answer/load/*", (req, res) => {
  setTimeout(() => {
    res.status(200).json(
      {
        "msg": {
          "id": 2,
          "answer": "这是一个问题的答案 答案 答案",
          "questionId": 7,
          "profileId": null,
          "approvalCount": -7,
          "publishTime": 1498008103000,
          "lastModifiedTime": 1498008405000,
          "publishTimeStr": "2017-06-21",
          "authorUserName": "薛定谔的猫",
          "authorHeadPic": "https://wx.qlogo.cn/mmopen/Q3auHgzwzM7wkhob9zgicD3IJxG1tLVSSe9qdzR1qUGXz6BwPv73sr67iaTEibcA1sNic3Roib4DgXCVG4IWe0zPAKJnlo5r4NibezssS6naic6dkM/0",
          "approval": true,
          "comments": [
            {
              "id": 2,
              "answerId": 2,
              "commentProfileId": null,
              "comment": "fff3333fff",
              "del": false,
              "repliedId": 1,
              "repliedProfileId": null,
              "repliedDel": true,
              "addTime": 1498009753000,
              "publishTimeStr": "2017-06-21",
              "authorUserName": "薛定谔的猫",
              "authorHeadPic": "https://wx.qlogo.cn/mmopen/Q3auHgzwzM7wkhob9zgicD3IJxG1tLVSSe9qdzR1qUGXz6BwPv73sr67iaTEibcA1sNic3Roib4DgXCVG4IWe0zPAKJnlo5r4NibezssS6naic6dkM/0",
              "repliedComment": "该评论已删除",
              "repliedName": "薛定谔的猫",
              "mine": true
            },
            {
              "id": 16,
              "answerId": 2,
              "commentProfileId": null,
              "comment": "dsfdsf",
              "del": false,
              "repliedId": 2,
              "repliedProfileId": null,
              "repliedDel": false,
              "addTime": 1498035861000,
              "publishTimeStr": "2017-06-21",
              "authorUserName": "三十文",
              "authorHeadPic": "https://wx.qlogo.cn/mmopen/bj9JGugn6UeXic2b4fpoXTsSC2qwW9WYLVQmx3Xs99RXQGZ7FV8J2NwUKjhGM06n0hQMXWr7icxdHpHTFxoqedzaIIicNwmz3cD/0",
              "repliedComment": "fff3333fff",
              "repliedName": "薛定谔的猫",
              "mine": false
            },
            {
              "id": 17,
              "answerId": 2,
              "commentProfileId": null,
              "comment": "hnhnhnhn",
              "del": false,
              "repliedId": null,
              "repliedProfileId": null,
              "repliedDel": null,
              "addTime": 1498035927000,
              "publishTimeStr": "2017-06-21",
              "authorUserName": "三十文",
              "authorHeadPic": "https://wx.qlogo.cn/mmopen/bj9JGugn6UeXic2b4fpoXTsSC2qwW9WYLVQmx3Xs99RXQGZ7FV8J2NwUKjhGM06n0hQMXWr7icxdHpHTFxoqedzaIIicNwmz3cD/0",
              "repliedComment": null,
              "repliedName": null,
              "mine": false
            }
          ],
          "mine": true,
          "topic": "测22222试"
        },
        "code": 200
      }
    )
  }, Math.random() * 1500)
})

router.post("/forum/answer/submit", (req, res) => {
  setTimeout(() => {
    res.status(200).json(
      {
        "msg": {
          "id": 0,
          "answer": "fff3飞飞飞飞333fff",
          "questionId": 1,
          "profileId": 16443,
          "approvalCount": 0,
          "publishTime": null,
          "lastModifiedTime": null,
          "publishTimeStr": "2017-06-21",
          "authorUserName": "风之伤",
          "authorHeadPic": "https://www.iqycamp.com/images/default_avatar.png",
          "approval": null,
          "comments": null,
          "mine": null,
          "topic": null
        },
        "code": 200
      }
    );
  }, Math.random() * 1500)
})

module.exports = router;
<file_sep>/src/modules/forum/commons/ForumButton/ForumButton.less
@import "../../variable.less";

// ForumButton
.forum-button-div {
  position: fixed;
  text-align: center;
  width: 100%;
  bottom: 0;
  left: 0;
  z-index:1000;
  height: 49px;
  border-top: 1px solid @color_weak;
  background-color: @color_white;
  .forum-button{
    margin: 8px auto;
    font-size: @font_context;
    border-radius: @border-radius;
    height: 30px;
    line-height: 30px;
    width: 150px;
    &.normal{
      color: @color_strong;
      border: 1px solid @color_strong;
      background-color: @color_white;
    }
    &.forbid{
      color: @color_help;
      border: 1px solid @color_help;
      background-color: @color_white;
    }
    &.primary{
      background-color: @color_strong;
      color: @color_white;
      border: 1px solid @color_strong;
    }
  }

}
<file_sep>/src/modules/home/components/article/ArticleHome.less
.article-home-component {
  display: block;
  position: relative;
  box-sizing: border-box;
  width: 100%;
  height: 9.6rem;
  margin: 2rem 0 4rem;
  .title {
    display: block;
    position: absolute;
    top: 2.5rem;
    font-size: 2.2rem;
    color: #ffffff;
    z-index: 2;
    width: 100%;
    text-align: center;
  }
  .desc {
    display: block;
    position: absolute;
    top: 6rem;
    font-size: 1.3rem;
    color: #ffffff;
    z-index: 2;
    width: 100%;
    text-align: center;
  }
  .thumbnail {
    position: absolute;
    z-index: 1;
    width: 100%;
    height: 100%;
    border-radius: 4px;
  }
}
<file_sep>/src/modules/forum/question/Question.tsx
import * as React from "react";
import { connect } from "react-redux";
import PullElement from 'pull-element';
import { ToolBarNoConnect } from "../../base/ToolBarNoConnect";
import { DialogHead } from "../commons/ForumComponent";
import { disFollow, follow, getAllQuestions, getQuestion, searchQuestion } from "../async";
import { mark } from "../../../utils/request"
import { splitText, removeHtmlTags, changeTitle, unScrollToBorder } from "../../../utils/helpers"
import { startLoad, endLoad, alertMsg, set } from "../../../redux/actions";
import _ from "lodash";
import QuestionAnswer from "./QuestionAnswer"
import FullScreenDialog from "../../../components/FullScreenDialog"
import "./Question.less";
import AssetImg from "../../../components/AssetImg";
import PullSlideTip from '../../../components/PullSlideTip'
import RenderInBody from '../../../components/RenderInBody'

interface QuestionStates {
  questions: object;
  // 分页获取 Question 列表分页数
  page: number;
  // 是否已是最后一页
  end: boolean;
  searching: boolean;
  init: boolean;
  questionId: number;
}

@connect(state => state)
export default class Question extends React.Component<any, QuestionStates> {

  constructor() {
    super()
    this.state = {
      questions: [],
      page: 1,
      end: false,
      showToast: false,
      content: "",
      searching: false,
      init: true,
      searchData: [],
      searchWord: '',
      windowInnerHeight: window.innerHeight,
      questionId: -1,
    }
    this.pullElement = null;
    this.timer = null;
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  componentWillMount() {
    changeTitle('论坛')
    mark({ module: "打点", function: "论坛", action: "打开问题列表页" })
    const { dispatch, location } = this.props;
    const { questionId } = location.query;
    let questions = [];
    dispatch(startLoad());
    if(questionId) {
      getQuestion(questionId).then(res => {
        if(res.code === 200) {
          questions.push(res.msg);
        } else {
          dispatch(alertMsg(res.msg));
        }
      }).catch(e => {
        dispatch(alertMsg(e));
      })
    }

    getAllQuestions().then(res => {
      dispatch(endLoad());
      const { code, msg } = res;
      if(code === 200) {
        if(questionId) {
          _.remove(res.msg.list, (item) => {
            return item.id == questionId;
          });
        }
        this.setState({ questions: questions.concat(msg.list) })
      } else {
        dispatch(alertMsg(res.msg));
      }
    }).catch(e => {
      dispatch(alertMsg(e));
      dispatch(endLoad());
    });

    //解决android键盘遮挡，改变屏幕高度问题
    this.timer = setInterval(() => this.handleKeyboardUp(), 200);

    dispatch(set('title', undefined));
  }

  componentDidMount() {
    // unScrollToBorder('.question-page');
  }

  componentDidUpdate() {
    const { dispatch, location } = this.props;
    if(!this.pullElement) {
      this.pullElement = new PullElement({
        target: '.question-page',
        scroller: '.question-page',
        // trigger: '.pull-slide-tips',
        damping: 4,
        detectScroll: true,
        detectScrollOnStart: false,
        onPullUp: (data) => {
          if(this.props.iNoBounce) {
            if(this.props.iNoBounce.isEnabled()) {
              this.props.iNoBounce.disable();
            }
          }
        },
        onPullUpEnd: () => {
          if(this.state.content) {
            searchQuestion(this.state.content, this.state.page + 1).then(res => {
              dispatch(endLoad());
              const { code, msg } = res;
              if(code === 200) {
                this.setState({
                  questions: this.state.questions.concat(msg.list),
                  page: this.state.page + 1
                })
              } else {
                dispatch(alertMsg(res.msg));
              }
            }).catch(e => {
              dispatch(alertMsg(e));
              dispatch(endLoad());
            });
          } else {
            getAllQuestions(this.state.page + 1).then(res => {
              const { code, msg } = res;
              const { questionId } = location.query;
              if(code === 200) {
                if(questionId) {
                  _.remove(res.msg.list, (item) => {
                    return item.id == questionId;
                  });
                }
                this.setState({
                  questions: this.state.questions.concat(msg.list),
                  page: this.state.page + 1,
                  end: msg.end
                })
              }
            }).catch(e => {
              dispatch(alertMsg(e));
              dispatch(endLoad());
            })
          }
          if(this.props.iNoBounce) {
            if(!this.props.iNoBounce.isEnabled()) {
              this.props.iNoBounce.enable();
            }
          }
        }
      })
      this.pullElement.init();
    }
    if(this.pullElement && this.state.end) {
      this.pullElement.destroy();
    }
  }

  componentWillUnmount() {
    if(this.timer) {
      clearInterval(this.timer);
    }
  }

  handleClickGoQuestionInitPage() {
    this.context.router.push("/forum/static/question/init")
  }

  handleClickFeedback() {
    mark({ module: "打点", function: "论坛", action: "点击意见反馈" });
    window.location.href = `https://${window.location.hostname}/survey/wjx?activity=15135162
`
  }

  handleClickGoAnswerPage(questionId) {
    this.setState({ questionId, show: true })
  }

  handleSearch(value) {
    const { dispatch } = this.props;
    const { searchWord } = this.state;
    if(searchWord === value) {
      return;
    }
    //不含字母时搜索
    let lastChar = value.charAt(value.length - 1);
    if(!/[A-Za-z]/.test(lastChar)) {
      dispatch(startLoad());
      searchQuestion(value, 1).then(res => {
        dispatch(endLoad());
        const { code, msg } = res;
        if(code === 200) {
          this.setState({ searchData: res.msg.list, content: value, page: 1, searchWord: value })
        } else {
          dispatch(alertMsg(res.msg));
        }
      }).catch(e => {
        dispatch(alertMsg(e));
        dispatch(endLoad());
      });
    }
  }

  handleCancel() {
    this.setState({ searching: false, init: true, searchData: [], searchWord: "" });
    this.refs.searchInput.value = '';
  }

  handleKeyboardUp() {
    const { windowInnerHeight, searchData = [] } = this.state;
    if(window.innerHeight > windowInnerHeight) {
      this.setState({ searchData, windowInnerHeight: window.innerHeight });
    }
    if(window.innerHeight < windowInnerHeight) {
      this.setState({ windowInnerHeight: window.innerHeight });
    }
  }

  closeDialog() {
    this.setState({ show: false })
  }

  render() {
    const { questions = [], init, searchData = [], show, questionId } = this.state;

    const renderSimpleQuestionList = () => {
      return (

          {
            searchData.map((questionItem, idx) => {
              const {
                id, topic
              } = questionItem
              return (

{splitText(removeHtmlTags(topic), 38)}

              )
            })
          }

      )
    }

    // 保留，解决 setState 之后重复 render 的问题
    const renderQuestionList = () => {
      return (

          {
            questions.map((questionItem, idx) => {
              const {
                addTimeStr, answerTips, authorHeadPic, authorUserName,
                description, id, topic
              } = questionItem

              // 如果是已关注，则显示已关注
              let tag = questionItem.follow
              let rightContent = tag ? '已关注' : '关注问题'
              const changeFollowStatus = () => {
                if(tag) {
                  // 已关注的情况，则调用取消关注接口
                  disFollow(id)
                } else {
                  // 未关注的情况，则调用关注接口
                  follow(id)
                }
                tag = !tag
                return tag ? '已关注' : '关注问题'
              }
              return (

                    <DialogHead
                      leftImgUrl={authorHeadPic} user={authorUserName} time={addTimeStr}
                      disableContentValue={`已关注`} rightContent={rightContent} rightContentFunc={changeFollowStatus}
                    />

{splitText(removeHtmlTags(topic), 38)}

                      {splitText(removeHtmlTags(description), 20)}

                      {answerTips}

                  <GreyBanner height="10px"/>

              )
            })
          }

      )
    }

    // 特殊组件
    const renderOtherComponents = () => {
      return (

          <RenderInBody>
            <ToolBarNoConnect noticeMsgCount={this.props.noticeMsgCount} tabIndex={this.props.tabIndex}
                              dispatch={this.props.dispatch} router={this.context.router}/>
          </RenderInBody>

      )
    }

    return (

 this.handleClickFeedback()}>意见反馈 >

        <div className={`question-page ${show ? '' : 'toolbar'}`}>

              <input type="text" className="search-input" placeholder='去搜索' ref="searchInput"
                     onClick={() => this.setState({ init: false })}
                     onChange={(e) => this.handleSearch(e.currentTarget.value)}
                     onBlur={(e) => this.handleSearch(e.currentTarget.value)}/>

          {init ?

              <AssetImg url="https://static.iqycamp.com/images/rise_icon_go_question.png" height={27}
                        style={{ verticalAlign: 'middle' }}/>

 :

 this.handleCancel()}>

取消

          }
          {init ? <GreyBanner height="20px"/> : <GreyBanner height="35px" content={'相关问题'}/>}
          <div style={{ display: `${init ? '' : 'none'}` }}>
            {renderQuestionList()}
            <PullSlideTip isEnd={this.state.end}/>

          <div style={{ backgroundColor: '#f5f5f5', display: `${init ? 'none' : ''}` }}>
            {renderSimpleQuestionList()}

        {show ?
          <FullScreenDialog close={() => this.closeDialog()} hash="#answer" level={1}>
            <QuestionAnswer questionId={questionId}/>
          </FullScreenDialog> :
          renderOtherComponents()}

    )
  }

}

class GreyBanner extends React.Component<{ height: number, content?: string }, any> {
  constructor() {
    super()
  }

  render() {
    return (

        {this.props.content}

    )
  }
}
<file_sep>/src/modules/plan/EventWall.tsx
import * as React from 'react'
import './EventWall.less'
import { connect } from 'react-redux'
import { set, startLoad, endLoad, alertMsg } from 'reduxutil/actions'
import { queryEventList } from './async'
import { ToolBar } from '../base/ToolBar'
import { changeTitle, goOtherWeb, unScrollToBorder } from '../../utils/helpers'
import { mark } from 'utils/request'
import Banner from '../../components/Banner'
import { MarkBlock } from '../../components/markblock/MarkBlock'

@connect(state => state)
export default class EventWall extends React.Component<any, any> {
  constructor(props) {
    super(props)
    this.state = {
      tab: 1
    }
    changeTitle('活动')

    this.bannerWidth = window.innerWidth
    this.bannerHeight = (175 / 375 * this.bannerWidth)
    this.barItemPd = ((70 / 750) * (window.innerWidth - 30)) / 2
    this.barItemWidth = ((window.innerWidth - 30) - this.barItemPd * 6) / 4

    if(this.barItemWidth < 64) {
      this.barItemWidth = 64
      this.barItemPd = ((window.innerWidth - 30) - (this.barItemWidth * 4)) / 6
    }
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  componentWillMount() {
    const { dispatch } = this.props
    dispatch(startLoad())
    mark({ module: '打点', function: '活动墙', action: '进入活动墙页面' })
    queryEventList().then(res => {
      dispatch(endLoad())
      if(res.code === 200) {
        let liveList = [], workList = [], offlineList = [], areaList = [], bannerList = []
        for(let i = 0; i < res.msg.length; i++) {
          switch(res.msg[i].type) {
            case 1:
              liveList.push(res.msg[i])
              break
            case 2:
              workList.push(res.msg[i])
              break
            case 3:
              offlineList.push(res.msg[i])
              break
            case 4:
              areaList.push(res.msg[i])
              break
            default:
              liveList.push(res.msg[i])
              break
          }
          if(res.msg[i].banner) {
            // 放到banner 注意，如果要做刷新，这里会有坑
            bannerList.push(res.msg[i])
          }
        }
        this.setState({
          liveList: liveList, workList: workList, offlineList: offlineList, areaList: areaList, bannerList: bannerList
        })
      } else {
        dispatch(alertMsg(res.msg))
      }
    }).catch(ex => {
      dispatch(endLoad())
      dispatch(alertMsg(ex))
    })
  }

  componentDidMount() {
    unScrollToBorder('.event-tab-body')
  }

  goEvent(item) {
    goOtherWeb(item.destUrl)
  }

  goBannerEvent(item) {
    goOtherWeb(item.destUrl)
  }

  renderTabBody() {
    const { liveList, workList, offlineList, areaList, tab } = this.state
    let tempList = null
    switch(tab) {
      case 2:
        tempList = liveList
        break
      case 1:
        tempList = workList
        break
      case 3:
        tempList = offlineList
        break
      case 4:
        tempList = areaList
        break
      default:
        tempList = liveList
        break
    }

    let height = this.bannerHeight > (window.innerHeight - 60) ? 'auto' : `${window.innerHeight - this.bannerHeight - 50 - 50}px`

    return (
      <div className="event-tab-body" style={{ height: `${height}` }}>

          {tempList ? tempList.map((item, idx) => {
            return (
              <MarkBlock module={'打点'} func={'活动墙'} action={'打开活动墙'} memo={item.id}
                         onClick={() => this.goEvent(item)} key={idx}>

                      {item.title}

{item.subHead}

                      {item.publisher}

                      {item.time}

              </MarkBlock>
            )
          }) : null}

没有更多了

    )
  }

  renderBanner() {
    const { bannerList } = this.state
    if(bannerList) {
      return (
        <Banner height={`17.5rem`}>
          {bannerList.map((item, key) => {
            return (
              <MarkBlock module={'打点'} func={'活动墙'} action={'打开Banner'} memo={item.id}
                         className="banner-item swiper-slide" onClick={() => this.goBannerEvent(item)} key={key}>

              </MarkBlock>
            )
          })}
        </Banner>
      )
    }
  }

  render() {
    return (
      <div className="layout"
           style={{ overflow: `${this.bannerHeight > (window.innerHeight - 60) ? 'auto' : 'hidden'}` }}>
        {this.renderBanner()}

            <div className={`navbar-item ${this.state.tab == 1 ? 'active' : ''}`}
                 onClick={e => this.setState({ tab: 1 })}>
              作业分析

            <div className={`navbar-item ${this.state.tab == 2 ? 'active' : ''}`}
                 onClick={e => this.setState({ tab: 2 })}>
              大咖直播

            <div className={`navbar-item ${this.state.tab == 3 ? 'active' : ''}`}
                 onClick={e => this.setState({ tab: 3 })}>
              线下活动

            <div
              className={`navbar-item ${this.state.tab == 4 ? 'active' : ''}`}
              onClick={e => this.setState({ tab: 4 })}>
              更多精彩

        {this.renderTabBody()}
        <ToolBar/>

    )
  }
}
<file_sep>/src/modules/forum/question/SubmitQuestionDetail.tsx
import * as React from "react"
import { connect } from "react-redux"
import { ForumButton } from "../commons/ForumComponent"
import { submitQuestion, getQuestion, loadTag } from "../async"
import { startLoad, endLoad, alertMsg, set } from "../../../redux/actions"
import { removeHtmlTags, changeTitle } from "../../../utils/helpers"
import { mark } from "../../../utils/request"
import Editor from "../../../components/simditor/Editor"
import _ from "lodash"
import "./SubmitQuestionDetail.less"

@connect(state => state)
export default class SubmitQuestionDetail extends React.Component<any, any> {

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  constructor() {
    super()
    this.state = {
      selectedTagList: [],
      tagList: [],
      index: 1,
      end: true,
      title: '',
      detail: '',
    }
  }

  componentWillMount() {
    changeTitle('论坛')
    mark({ module: "打点", function: "论坛", action: "打开提问详情页面" })
    const { dispatch, location, title } = this.props
    const { questionId } = location.query
    if(questionId) {
      //修改提问
      dispatch(startLoad())
      getQuestion(questionId).then(res => {
        const { code, msg } = res
        dispatch(endLoad())
        if(code === 200) {
          this.setState({
            title, detail: msg.description,
            selectedTagList: msg.forumTags
          })
        } else {
          dispatch(alertMsg(msg))
        }
      }).catch(ex => {
        dispatch(endLoad())
        dispatch(alertMsg(ex))
      })
    } else {
      //提问
      loadTag().then(
        res => {
          dispatch(endLoad())
          if(res.code === 200) {
            this.setState({ tagList: res.msg, title })
          } else {
            dispatch(alertMsg(res.msg))
          }
        }
      ).catch(ex => {
        dispatch(endLoad())
        dispatch(alertMsg(ex))
      })
    }
  }

  submit() {
    const { dispatch, location } = this.props
    const { title, selectedTagList, tagList } = this.state
    const detail = this.refs.editor.getValue()
    if(!detail) {
      dispatch(alertMsg('问题描述不能为空哦'))
      return
    }

    if(removeHtmlTags(detail).length > 1000) {
      dispatch(alertMsg('问题描述不能超过1000个字哦'))
      return
    }

    let tagIds = []

    // 第一次提问tagList不为空
    if(tagList.length > 0) {
      tagList.forEach(selectedTag => {
        if(selectedTag.selected)
          tagIds.push(selectedTag.id);
      });
    }
    // 修改问题时selectedTagList不为空
    if(selectedTagList.length > 0) {
      selectedTagList.forEach(tag => {
        tagIds.push(tag.id)
      })
    }

    if(tagIds.length === 0) {
      dispatch(alertMsg('请先选择标签哦'))
      return
    }

    dispatch(startLoad())
    const { questionId } = location.query
    if(questionId) {
      submitQuestion({ topic: title, description: detail, tagIds, questionId }).then(res => {
          dispatch(endLoad())
          if(res.code === 200) {
            this.context.router.push({ pathname: '/forum/static/question', query: { questionId } })
          } else {
            dispatch(alertMsg(res.msg))
          }
        }
      ).catch(ex => {
        dispatch(endLoad())
        dispatch(alertMsg(ex))
      })
    } else {
      submitQuestion({ topic: title, description: detail, tagIds }).then(res => {
          dispatch(endLoad())
          if(res.code === 200) {
            this.context.router.push({ pathname: '/forum/static/question', query: { questionId: res.msg } })
          } else {
            dispatch(alertMsg(res.msg))
          }
        }
      ).catch(ex => {
        dispatch(endLoad())
        dispatch(alertMsg(ex))
      })
    }
  }

  onClick(tag) {
    const { dispatch } = this.props
    const { tagList } = this.state
    let selectedTags = []
    //已选中则删除，反之则选中
    tagList.forEach((item) => {
      if(item.id === tag.id) {
        item.selected = !tag.selected
      }
      if(item.selected) {
        selectedTags.push(item)
      }
    })
    if(selectedTags.length > 3) {
      dispatch(alertMsg("最多选择 3 个问题标签"))
      this.onClick(tag)
    }
    this.setState({ tagList: tagList })
  }

  render() {
    const { tagList = [], title, selectedTagList = [] } = this.state
    const renderLine = (tagLineList) => {
      return (

          {tagLineList.map((tag, idx) => {
            return (
              <div className={`${tag.selected ? 'tag-selected' : 'tag'}`}
                   key={idx} onClick={() => this.onClick(tag)}>
                {tag.name}

            )
          })}

      )
    }

    const renderTagList = () => {
      // 包含所有的行元素的数组
      let tagAll = []
      // 一共渲染几行
      let line = tagList.length / 3
      for(let j = 0; j < line; j++) {
        // 一行标签的数组
        let tagLineList = []
        for(let i = 0; i < 3; i++) {
          if(j * 3 + i === tagList.length) {
            break
          }
          tagLineList.push(tagList[ j * 3 + i ])
        }
        tagAll.push(tagLineList)
      }
      return (

            选择标签

            {
              tagAll.map((tagLineList, idx) => {
                return (
                  renderLine(tagLineList)
                )
              })
            }

      )
    }

    // 渲染文本样式 tag 列表
    const renderTagListText = () => {
      let tagContent = ''
      if(selectedTagList.length > 0) {
        tagContent = tagContent.concat('已选标签：')
        selectedTagList.map((tag, idx) => {
          if(idx === 0) {
            tagContent = tagContent.concat(tag.name)
          } else {
            tagContent = tagContent.concat(` | ${tag.name}`)
          }
        })
      }
      return (

{tagContent}

      )
    }

    const renderButton = () => {
      return (
        <ForumButton content={'提交'} clickFunc={() => this.submit()}/>
      )
    }

    const renderEditor = () => {
      return (

          <Editor
            toolbarFloat={false}
            ref="editor"
            moduleId="5"
            maxLength="1000"
            scrollContainer="question-detail-container"
            value={this.state.detail}
            placeholder="写下问题的详细背景，帮助他人更好地分析和解答你的问题（1000字以内）。"
          />

      )
    }

    return (

            {title}

          {_.isEmpty(selectedTagList) ? renderTagList() : renderTagListText() }

            {renderEditor()}

          {renderButton()}

    )

  }
}
<file_sep>/src/modules/forum/commons/DialogBottom/DialogBottomIcon.tsx
import * as React from "react"
import AssetImg from "../../../../components/AssetImg";
import "./DialogBottomIcon.less"

// 问题概要 底部组件
interface DialogBottomProps {
  leftContent?: string;
  leftContentFunc?: object;
  btn1ImgUrl?: string;
  btn1Content?: number;
  btn1ContentFunc?: object;
  btn2ImgUrl?: string;
  btn2Content?: number;
  btn2ContentFunc?: object;
}
interface DialogBottomStates {
  changeLeftContent: string;
  changeBtn2ImgUrl: string;
  changeBtn2Content: number;
}
const comment = 'https://static.iqycamp.com/images/fragment/comment.png?imageslim'
const unvote = 'https://static.iqycamp.com/images/fragment/unvote.png?imageslim'
const voted = 'https://static.iqycamp.com/images/fragment/voted.png?imageslim'
export default class DialogBottomIcon extends React.Component<DialogBottomProps, DialogBottomStates> {

  constructor() {
    super()
    this.state = { changeLeftContent:'', changeBtn2ImgUrl: '', changeBtn2Content: 0}
  }

  componentWillMount() {
    if(this.props.btn2ImgUrl) {
      this.setState({
        changeLeftContent: this.props.leftContent,
        changeBtn2ImgUrl: this.props.btn2ImgUrl ,
        changeBtn2Content: this.props.btn2Content
      })
    }
  }

  render() {
    const {
      leftContent, leftContentFunc = () => {},
      btn1ImgUrl, btn1Content, btn1ContentFunc = () => {},
      btn2ImgUrl, btn2Content, btn2ContentFunc = () => {}
    } = this.props
    const { changeLeftContent, changeBtn2ImgUrl, changeBtn2Content} = this.state

    const renderLeftContent = () => {
      if(!leftContent) return
      const changeLeftContentFunc = () => {
        let funcReturnContent = leftContentFunc()
        this.setState({changeLeftContent: funcReturnContent ? funcReturnContent : leftContent})
      }
      return (

 changeLeftContentFunc()}>
          {changeLeftContent}

      )
    }

    const renderBtn1Kit = () => {
      const renderBtn1 = btn1ImgUrl || btn1Content
      if(renderBtn1) {
        return (

 btn1ContentFunc()}><AssetImg url={btn1ImgUrl} size={20}/>

 btn1ContentFunc()}>{btn1Content}

        )
      }
    }

    const renderBtn2Kit = () => {
      const renderBtn2 = btn2ImgUrl || btn2Content
      const changeBtn2ImgUrlFunc = () => {
        let funcReturnContent = btn2ContentFunc()
        this.setState({
          changeBtn2ImgUrl: funcReturnContent ? funcReturnContent : btn2ImgUrl,
          changeBtn2Content: funcReturnContent === voted ? this.state.changeBtn2Content + 1 : this.state.changeBtn2Content - 1
        })
      }

      if(renderBtn2) {
        return (

 changeBtn2ImgUrlFunc()}><AssetImg url={changeBtn2ImgUrl} size={20}/>

 changeBtn2ImgUrlFunc()}>
              {changeBtn2Content}

        )
      }
    }

    return (

        {renderLeftContent()}

          {renderBtn1Kit()}
          {renderBtn2Kit()}

    )
  }
}
<file_sep>/src/components/FullScreenDialog.less
.full-screen-dialog{
  top: 0;
  left: 0;
  z-index: 1;
  position: fixed;
  height: 100%;
  width: 100%;
  background-color: #fff;
  overflow: scroll;
}
<file_sep>/src/components/RiseCell.less
@import "../style/variable";

.weui_cell.rise-cell {
  &:before {
    border-top: none;
  }
  padding: 10px 25px;
  &:after {
    content: " ";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 1px;
    border-top: 1px solid @color_weak_background;
    color: @color_weak_background;
    -webkit-transform-origin: 0 0;
    transform-origin: 0 0;
    -webkit-transform: scaleY(.5);
    transform: scaleY(.5);
  }
  &.access {
    .rise-cell-footer:after {
      content: " ";
      display: inline-block;
      -webkit-transform: rotate(45deg) translateY(-70%);
      transform: rotate(45deg) translateY(-70%);
      height: 8px;
      width: 8px;
      border-width: 2px 2px 0 0;
      border-color: #c8c8cd;
      border-style: solid;
      position: absolute;
      top: 50%;
      right: 16px;
    }
  }
  .rise-cell-body {
    font-size: 14px;
    color: #666666;
    height: 30px;
    line-height: 30px;
  }
  .rise-cell-footer {
    font-size: 14px;
    min-width: 50px;
    &.has-value {
      span {
        color: #333;
      }
      input {
        color: #333;
      }
    }
    input {
      text-align: right;
      border: none;
      line-height: 100%;
      font-size: 14px;
      &::-webkit-input-placeholder { /* WebKit browsers */
        color: @color_help;
      }
      &::-moz-placeholder { /* Mozilla Firefox 4 to 18 */
        color: @color_help;
      }
      &::-moz-placeholder { /* Mozilla Firefox 19+ */
        color: @color_help;
      }
      &::-ms-input-placeholder { /* Internet Explorer 10+ */
        color: @color_help;
      }
    }

    .form-item {
      display: inline;
      .pc-box {
        display: inline;
        .show_contact {
          display: inline-block;
        }
      }
    }
  }
}
<file_sep>/src/modules/forum/commons/DialogHead/DialogHead.less
@import "../../variable.less";

// DialogHead
.dialog-head {
  height: 35px;
  position: relative;
  margin-top: 3px;

  .dialog-left {
    height: 100%;
    display: inline-block;

    .dialog-left-headpic {
      height: 32.5px;
      .assetImg {
        border-radius: 50%;
      }
    }
    .dialog-left-content {
      height: 32.5px;
      padding: 3px 5px;
      position: absolute;

      .dialog-name {
        font-size: 1.3em;
        line-height: 1.3em;
        color: @color_title;
      }
      .dialog-time {
        font-size: 1.1em;
        line-height: 1.1em;
        color: @color_weak;
      }
      span {
        display: block;
      }
    }

    div {
      display: inline-block;
    }
  }
  .dialog-right {
    height: 100%;
    position: absolute;
    right: 0;
    display: inline-block;

    .dialog-right-content {
      height: 21px;
      color: #ff8c2b;
      text-align: center;
      font-size: 1.3em;
      line-height: 21px;
      border-radius: 5px;
      border: 1px solid #ff8c2b;
      padding: 0 5px;

      &.disable {
        color: @color_help;
        border: 1px solid @color_help;
      }
    }
    div {
      display: inline-block;
    }
  }
}
<file_sep>/src/modules/forum/ForumBase.less
@import "variable.less";

.forumbase-container {
  //font-size: 62.5%;

  .forumbase-page {
    background-color: @color_white;
  }
}
<file_sep>/src/modules/home/components/article/ArticleHome.tsx
import * as React from 'react'
import './ArticleHome.less'
import AssetImg from '../../../../components/AssetImg'
import { splitContentWithSuffix } from '../../../../utils/helpers'
import { mark } from '../../../../utils/request'

interface ArticleHomeProps {
  data: any
}

export class ArticleHome extends React.Component<ArticleHomeProps, any> {

  constructor () {
    super()
    this.state = {
      data: {},
    }
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired,
  }

  render () {
    const {
      title = '',
      description = '',
      thumbnail = '',
      linkUrl = '',
      linkParam = '',
    } = this.props.data

    return (

 {
        mark({ module: '打点', function: '着陆页', action: '点击文章' })
        window.location.href = linkUrl
      }}>

{splitContentWithSuffix(title, 12)}

{splitContentWithSuffix(description, 20)}

        <AssetImg className="thumbnail" url={thumbnail}></AssetImg>

    )
  }

}
<file_sep>/src/components/Sidebar.less
@import "../style/variable";

.side-bar-controller{
  position: absolute;
  top: 45px;
  left:100%;
  width:50px;
  height:30px;
  background-color:#7FD8F0;
  color:#FFF;
  font-size:1.5em;
  font-weight:bolder;
  line-height:30px;
  text-align: center;
  border-radius:0 30px 30px 0;
  -webkit-box-shadow: 3px 2px 2px rgba(0, 0, 0, 0.3);
  -moz-box-shadow: 3px 2px 2px rgba(0, 0, 0, 0.3);
  box-shadow: 3px 2px 2px rgba(0, 0, 0, 0.3);
  > span{
    margin-left:-5px;
  }
  -webkit-transform: translateX(-50px);
  -moz-transform: translateX(-50px);
  -ms-transform: translateX(-50px);
  -o-transform: translateX(-50px);
  transform: translateX(-50px);
  -webkit-transition: transform 1s;
  -moz-transition: transform 1s;
  -ms-transition: transform 1s;
  -o-transition: transform 1s;
  transition: transform 1s;
  &.open{
    -webkit-transform: translateX(0px);
    -moz-transform: translateX(0px);
    -ms-transform: translateX(0px);
    -o-transform: translateX(0px);
    transform: translateX(0px);
  }
}
<file_sep>/src/modules/forum/question/SubmitQuestionInit.tsx
import * as React from "react";
import { connect } from "react-redux";
import { ForumButton } from "../commons/ForumComponent";
import { searchQuestion, getQuestion } from "../async"
import { mark } from "../../../utils/request"
import { splitText, removeHtmlTags, changeTitle } from "../../../utils/helpers"
import { startLoad, endLoad, alertMsg, set } from "../../../redux/actions";
import "./SubmitQuestionInit.less"
import _ from "lodash"
import PullSlideTip from '../../../components/PullSlideTip'

@connect(state => state)
export default class SubmitQuestionInit extends React.Component<any, any> {

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  constructor() {
    super();
    this.state = {
      data: [],
      tagList: [],
      index: 1,
      end: true,
      selected: true,
      searchWord: '',
      searchData: [],
      length: 0,
    };
    this.timer = null;
  }

  componentWillMount() {
    changeTitle('论坛')
    mark({ module: "打点", function: "论坛", action: "打开提问标题页面" });
    const { dispatch, location, title } = this.props;
    const { questionId } = location.query;
    if(questionId) {
      getQuestion(questionId).then(res => {
        const { msg, code } = res;
        if(code === 200) {
          this.setState({
            title: msg.topic, length: msg.topic.length
          });
        } else {
          dispatch(alertMsg(msg));
        }
      }).catch(e => {
        dispatch(alertMsg(e));
      })
    }

    if(title) {
      this.setState({
        title, length: title.length
      });
    }

    //解决android键盘遮挡，改变频幕高度问题
    this.timer = setInterval(() => this.handleKeyboardUp(), 20);
  }

  componentWillUnmount() {
    if(this.timer) {
      clearInterval(this.timer);
    }
  }

  nextTask() {
    const { dispatch, location } = this.props;
    const { title } = this.state;
    const { questionId } = location.query;
    if(!title) {
      dispatch(alertMsg('请先写问题标题'))
      return
    }
    dispatch(set('title', title));
    //保证android的屏幕高度恢复后再跳转
    dispatch(startLoad())
    setTimeout(() => {
      dispatch(endLoad())
      if(questionId) {
        this.context.router.push({ pathname: '/forum/static/question/detail', query: { questionId } });
      } else {
        this.context.router.push('/forum/static/question/detail');
      }
    }, 1000);

  }

  writeTitle(title) {
    const { dispatch } = this.props;
    const { searchWord } = this.state;
    if(searchWord === title && !!title) {
      return;
    }

    if(!title) {
      this.setState({ data: [], page: 1, length: 0, title: '' });
      return;
    }

    //不含字母时搜索
    let lastChar = title.charAt(title.length - 1);
    this.setState({ title, length: title.length });
    if(!/[A-Za-z]/.test(lastChar)) {
      searchQuestion(title, 1).then(res => {
        const { code, msg } = res;
        if(code === 200) {
          this.setState({ data: msg.list, page: 1 });
        } else {
          dispatch(alertMsg(msg));
        }
      }).catch(e => {
        dispatch(alertMsg(e));
      });
    }
  }

  handleKeyboardUp() {
    const { windowInnerHeight } = this.state;
    if(window.innerHeight > windowInnerHeight) {
      this.render();
    }
    this.setState({ windowInnerHeight: window.innerHeight });
  }

  handleClickGoAnswerPage(questionId) {
    const { dispatch } = this.props;
    const { title } = this.state;
    dispatch(set('title', title));
    //保证android的屏幕恢复后再跳转
    dispatch(startLoad());
    setTimeout(() => {
      dispatch(endLoad());
      this.context.router.push({
        pathname: "/forum/static/answer",
        query: { questionId }
      })
    }, 1000);
  }

  render() {
    const { data = [], end, length, title } = this.state;

    const renderQuestionList = () => {
      if(!_.isEmpty(data)) {
        return (

            {
              data.map((questionItem, idx) => {
                const {
                  id, topic
                } = questionItem;
                return (

{splitText(removeHtmlTags(topic), 38)}

                )
              })
            }

        )
      }
    }

    const renderButton = () => {
      return (
        <ForumButton content={'下一步'} clickFunc={() => this.nextTask()}/>
      )
    }

    return (

        <div className={`question-page ${_.isEmpty(data) ? '' : 'selected'}`}>

            写下问题标题

            <textarea
              placeholder="写下问题的标题吧，清晰的标题能够吸引更多的人来回答问题（50字以内）"
              id="textarea" maxLength={50} defaultValue={title} value={title}
              onChange={(e) => this.writeTitle(e.currentTarget.value)}
            />

                {length} / 50

          {_.isEmpty(data) ? null :

              相关的问题

          }
          {renderQuestionList()}
          {renderButton()}

    )

  }

}
<file_sep>/src/modules/forum/commons/SimpleQuestion/SimpleQuestion.less
@import "../../variable.less";

// HeadAreaProps
.simple-question {
  width: 100%;
  background-color: @color_white;
  border-bottom: 1px solid @color_weak_background;

  .question-topic {
    font-size: @font_context;
    line-height: @font_context;
    color: @color_context;
    margin-top: 8px;
  }
  .question-info {
    margin-bottom: 8px;
    span{
      font-size: @font_help;
      line-height: @font_help;
      color: @color_help;
    }
  }

}

<file_sep>/src/modules/forum/ForumBase.tsx
import * as React from 'react'
import "./ForumBase.less"

export default class ForumBase extends React.Component {

  constructor() {
    super()
  }

  componentDidMount() {
    document.querySelector('#react-app').className = 'scrollable';
  }

  componentWillUnmount() {
    document.querySelector('#react-app').className = '';
  }

  render() {
    return (

          {this.props.children}

    )
  }

}
<file_sep>/src/components/QYVideo.tsx
import * as React from 'react'
import './QYVideo.less'
import { mark } from '../utils/request'
import enableInlineVideo from 'iphone-inline-video'

export default class QYVideo extends React.Component<any, any> {

  constructor() {
    super()
    this.state = {}
  }

  handleClickShowWords(showWords) {
    const { beforeShowWords, cantShowWords } = this.props
    if(!showWords) {
      // 原来是关闭的，现在展开
      if(beforeShowWords) {
        beforeShowWords().then(res => {
          if(res.code === 200) {
            if(res.msg !== 'ok') {
              if(cantShowWords) {
                cantShowWords(res.msg)
              }
            } else {
              this.setState({ showWords: !showWords })
            }
          }
        })
      } else {
        this.setState({ showWords: !showWords })
      }
      mark({ module: '打点', function: '视频', action: '查看视频文稿', memo: this.props.url })
    } else {
      this.setState({ showWords: !showWords })
    }
  }

  render() {
    const { videoUrl, videoPoster, videoWords } = this.props
    const { showWords } = this.state
    return (

        <video  ref="video" src={videoUrl} poster={videoPoster} controls="controls" width="100%">您的设备不支持Video标签
        </video>
        {videoWords &&
        <div className={`video-words-container ${showWords ? 'show-all' : 'hide'}`}>
          <div className={`video-words`} dangerouslySetInnerHTML={{ __html: videoWords }}/>
          <div className={`words-text-mask`}>
            <div className={`words-mask-tips`} onClick={() => this.handleClickShowWords(showWords)}>
            <span className={`awb-tips ${showWords ? 'hide' : 'show'}`}>
              {showWords ? '收起' : '查看视频文稿'}

        }

    )
  }
}

<file_sep>/src/modules/forum/commons/HeadArea/HeadArea.tsx
import * as React from "react"
import "./HeadArea.less"

// 当网页最上层导航
interface HeadAreaProps {
  content: string;
  btnContent?: string;
  btnFunc?: object;
}
export default class HeadArea extends React.Component<HeadAreaProps, any> {

  constructor() {
    super()
  }

  render() {
    const {
      content, btnContent = '', btnFunc = () => {
      }
    } = this.props;

    const renderHeadBtn = () => {
      if(btnContent !== '') {
        return
 btnFunc()}>{btnContent}

      }
    }

    return (

{content}

        {renderHeadBtn()}

    )
  }

}
<file_sep>/src/modules/forum/commons/ForumButton/ForumButton.tsx
import * as React from "react"
import "./ForumButton.less"

interface ForumButtonProps {
  content: string;
  clickFunc?: object;
  forbid?: boolean;
  primary?: boolean;
}

export default class ForumButton extends React.Component<ForumButtonProps, any> {

  constructor() {
    super()
  }

  render() {
    const {
        content, clickFunc = ()=>{}, forbid, primary
    } = this.props

    return (

          {forbid ?
              <div className={`forum-button forbid`}>
                {content}

:
              <div className={`forum-button ${primary? 'primary':'normal'}`} onClick={()=>clickFunc()}>
                {content}

          }

    )
  }

}
<file_sep>/src/modules/home/LandingPage.less
.landing-page-container {
  box-sizing: border-box;
  padding: 5rem 2rem 8rem;
  background-color: #fff;
  > .header {
    position: fixed;
    width: 100%;
    height: 5rem;
    top: 0;
    left: 0;
    z-index: 10;
    background-color: #fff;
    .left-header-box {
      position: absolute;
      display: inline-block;
      width: 2rem;
      height: 2rem;
      top: 50%;
      left: 2rem;
      transform: translateY(-50%);
      .message {
        position: absolute;
        display: inline-block;
        width: 2rem;
        height: 2rem;
        left: 0;
        top: 0;
        line-height: 5rem;
      }
      .notify {
        position: absolute;
        display: inline-block;
        width: 6px;
        height: 6px;
        top: 1px;
        right: -1px;
        background-color: #ff983f;
        border-radius: 50%;
      }
    }
    .right-header-box {
      position: absolute;
      display: inline-block;
      top: 50%;
      right: 2rem;
      height: 2rem;
      transform: translateY(-50%);
      > span {
        display: inline-block;
        font-size: 1.3rem;
        line-height: 2rem;
        color: #ff8821;
        float: left;
      }
      .consult-icon {
        width: 2rem;
        height: 2rem;
        display: inline-block;
        float: left;
      }
    }
  }
  > .home-swiper {
    height: 16rem;
    padding-bottom: 2rem;
    .swiper-image {
      width: 100%;
      height: 16rem;
      border-radius: 4px;
    }
  }
  > .business-apply {
    margin-top: 2rem;
    width: 100%;
    height: 9.4rem;
    background: url("https://static.iqycamp.com/apply_participate_business_school-4o57ut6m.png?imageslim") no-repeat center center / 100% 9.4rem;
    border-radius: 4px;
  }
  .content-box-container {
    > .content-box {
      &:first-of-type {
        margin-top: 2rem;
      }
      margin-top: 6rem;
      .content-header {
        position: relative;
        height: 3rem;
        .content-title {
          position: absolute;
          left: 0;
          top: 0;
          font-size: 2.2rem;
          color: #333333;
          font-weight: bolder;
        }
        .more {
          position: absolute;
          right: 0;
          top: 0;
          line-height: 3rem;
          font-size: 1.5rem;
          color: #999;
        }
      }
    }
  }
  > .bottom-text {
    font-size: 1.3rem;
    color: #666666;
    text-align: center;
  }
}
<file_sep>/src/modules/customer/PointTip.less
@import "../../style/variable";

.point-tip{
  width: 100%;
  height:100%;
  overflow-y: scroll;
  background-color: #FFF;
  > .point-tip-title{
    background-color: #ffffff;
    height:55px;
    padding-left:15px;
    line-height:55px;
    color:@color_title;
    border-bottom: 1px solid @color_weak_background;
    font-size: 13px;
  }
  > .point-tip-container{
    //background-color: #ffffff;
    padding: 10px 15px;
    font-size:13px;
    color:@color_context;
    ul{
      margin-left:1em;
    }
    .pic{
      width:80%;
      height:auto;
      margin:0 auto;
      display: block;
    }
  }
  .rocket-container{
    min-height:200px;
    //width:80%;
    height:100%;
    margin:0 auto;
    > .rocket{
      display: block;
      margin:0 auto;
      padding: 15px 0 20px;
    }
    //background: url('') no-repeat center center/100% auto;
  }
  .button{
    margin:0 75px;
    height:50px;
    text-align: center;
    line-height:50px;
    color:#FFF;
    -webkit-border-radius: 4px;
    -moz-border-radius: 4px;
    border-radius: 4px;
    background:linear-gradient(to left top,#08d5fa 20%,#2ca4fb)
  }

}
<file_sep>/src/modules/home/LandingPage.tsx
import * as React from 'react'
import './LandingPage.less'
import { ProblemHome } from './components/problem/ProblemHome'
import { LiveHome } from './components/live/LiveHome'
import { ArticleHome } from './components/article/ArticleHome'
import { ActivityHome } from './components/activity/ActivityHome'
import { ColumnSpan } from '../../components/ColumnSpan'
import { changeTitle, formatDate, lockWindow, unlockWindow } from '../../utils/helpers'
import * as FontAwesome from 'react-fontawesome'
import Banner from '../../components/Banner'
import { loadLandingPageData, loadShuffleArticles } from './async'
import AssetImg from '../../components/AssetImg'
import { ToolBar } from '../base/ToolBar'
import { mark } from '../../utils/request'
import { connect } from 'react-redux'
import { startLoad, endLoad, alertMsg, set } from 'reduxutil/actions'
import { Dialog } from 'react-weui'
import { MarkBlock } from '../../components/markblock/MarkBlock'
import * as _ from 'lodash';

const { Alert } = Dialog
@connect(state => state)
export default class LandingPage extends React.Component {

  constructor() {
    super()
    this.state = {
      data: {},
      dialogButtons: [
        {
          label: '知道了',
          onClick: (e) => {
            const { applySuccess = {} } = this.state;
            let newApplySuccess = _.merge(_.cloneDeep(applySuccess), { isShowPassNotify: false });
            this.setState({ applySuccess: newApplySuccess })
          },
        },
        {
          label: '立即入学',
          onClick: (e) => {
            const { applySuccess = {} } = this.state;
            let newApplySuccess = _.merge(_.cloneDeep(applySuccess), { isShowPassNotify: false });
            this.setState({ applySuccess: newApplySuccess }, () => {
              window.location.href = `/pay/apply?goodsId=${applySuccess.goPayMemberTypeId}`
            })
          },
        },
      ],
      applySuccess: {},
    }
  }

  countDownTimer

  static contextTypes = {
    router: React.PropTypes.object.isRequired,
  }

  async componentWillMount() {
    changeTitle('圈外同学')
    mark({ module: '打点', function: '着陆页', action: '打开着陆页' })
    let res = await loadLandingPageData()
    if(res.code === 200) {
      this.setState({
        data: res.msg,
        applySuccess: res.msg.applySuccess
      }, () => {
        if(res.msg.applySuccess.isShowPassNotify) {
          this.countDownTimer = setInterval(() => {
            const { applySuccess = {} } = this.state;
            let newApplySuccess = _.cloneDeep(applySuccess);
            newApplySuccess = _.merge(newApplySuccess, { remainTime: applySuccess.remainTime - 1000 > 0 ? applySuccess.remainTime - 1000 : 0 })
            this.setState({ applySuccess: newApplySuccess })
          }, 1000)
        }
      })
    } else {
      dispatch(alertMsg(res.msg))
    }
  }

  componentWillUnmount() {
    clearInterval(this.countDownTimer)
  }

  handleClickImageBanner(banner) {
    if(banner.linkUrl.indexOf('http') >= 0) {
      window.location.href = banner.linkUrl
    } else {
      this.context.router.push(banner.linkUrl)
    }
  }

  async shuffleArticles() {
    let res = await loadShuffleArticles()
    if(res.code === 200) {
      let data = this.state.data
      data.articlesFlows = res.msg
      this.setState({
        data: data,
      })
    } else {
      dispatch(alertMsg(res.msg))
    }
  }

  formatSeconds(value: number): { remainHour, remainMinute, remainSecond } {
    var remainSecond = parseInt(value);// 秒
    var remainMinute = 0;// 分
    var remainHour = 0;// 小时
    if(remainSecond > 60) {
      remainMinute = parseInt(remainSecond / 60);
      remainSecond = parseInt(remainSecond % 60);
      if(remainMinute > 60) {
        remainHour = parseInt(remainMinute / 60);
        remainMinute = parseInt(remainMinute % 60);
      }
    }
    if(remainSecond <= 0) {
      remainSecond = 0;
    }

    return { remainHour, remainMinute, remainSecond };
  }

  formatDateString(date) {
    // { remainHour, remainMinute, remainSecond }
    let dateInfo = this.formatSeconds(date / 1000);
    return `${dateInfo.remainHour}时${dateInfo.remainMinute}分${dateInfo.remainSecond}秒`
  }

  render() {
    const {
      notify = false,
      isBusinessMember = true,
      pageBanners = [],
      problemsFlows = [],
      livesFlows = [],
      articlesFlows = [],
      activitiesFlows = [],
    } = this.state.data

    return (

 this.context.router.push('/rise/static/message/center')}>
            <AssetImg className="message" url="https://static.iqycamp.com/icon_message@2x-8rkrc4h9.png"/>
            {notify &&

}

          <MarkBlock module="打点"
                     func="着陆页"
                     action="点击入学咨询"
                     className="right-header-box"
                     onClick={() => _MEIQIA('showPanel')}>
            入学咨询
            <AssetImg className="consult-icon" url="https://static.iqycamp.com/icon_goutong @2x-8ww0p3at.png"/>
          </MarkBlock>

          {
            pageBanners.length > 0 &&
            <Banner height='16rem'>
              {pageBanners.map((banner, index) =>
                 {
                  mark({ module: '打点', function: '着陆页', action: '点击Banner' })
                  this.handleClickImageBanner(banner)
                }} className="banner-item swiper-slide swiper-image"/>,
              )}
            </Banner>
          }

        <ColumnSpan height="10" style={{ margin: '0 -2rem' }}/>
        {
          !isBusinessMember &&
          <MarkBlock module="打点"
                     func="着陆页"
                     action="申请加入商学院"
                     className="business-apply"
                     onClick={() => window.location.href = `/pay/rise`}></MarkBlock>
        }

          {
            !isBusinessMember &&

圈外课

              {problemsFlows.map((problem, index) => {
                return <ProblemHome data={problem} key={index} subscribeFunc={() => this.subscribeProblem()}/>
              })}

          }

拓眼界

              {
                livesFlows.length > 5 &&
                <MarkBlock module="打点"
                           func="着陆页"
                           action="拓眼界更多"
                           className="more"
                           onClick={() => this.context.router.push('/rise/static/home/lives')}>
                  更多
                  <FontAwesome name="angle-right"/>
                </MarkBlock>
              }

            {livesFlows.slice(0, 5).map((live, index) => <LiveHome data={live} key={index}/>)}

加油站

              <MarkBlock module="打点" func="着陆页" action="加油站换一换" className="more" onClick={() => this.shuffleArticles()}>
                换一换
                <FontAwesome name="refresh"/>
              </MarkBlock>

            {articlesFlows.slice(0, 3).map((article, index) => <ArticleHome data={article} key={index}/>)}

圈柚会

              {
                activitiesFlows.length > 3 &&
                <MarkBlock module="打点"
                           func="着陆页"
                           action="圈柚会更多"
                           className="more"
                           onClick={() => this.context.router.push('/rise/static/home/activities')}>
                  更多
                  <FontAwesome name="angle-right"/>
                </MarkBlock>
              }

            {activitiesFlows.slice(0, 3).map((activity, index) => <ActivityHome data={activity} key={index}/>)}

我也是有底线的...

        <Alert show={this.state.applySuccess.isShowPassNotify} buttons={this.state.dialogButtons}>
          恭喜你通过{this.state.applySuccess.name}申请！

          离入学截止时间还剩{this.state.applySuccess.remainTime ? this.formatDateString(this.state.applySuccess.remainTime) : ''}

          点击立即入学，开启圈外商学院之旅
        </Alert>
        <ToolBar/>

    )
  }

}
<file_sep>/src/modules/promotion/grouplearning/GroupPromotionWaiting.tsx
import * as React from 'react'
import './GroupPromotionWaiting.less'

interface GroupPromotionWaitingProps {
  leaderName: string,
  remainderCount: number,
  groupCode: string
}

export class GroupPromotionWaiting extends React.Component<GroupPromotionWaitingProps, any> {

  constructor() {
    super()
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  render() {
    const { leaderName = '', remainderCount = 1, groupCode = '' } = this.props

    return (

        你已接受{leaderName}的邀请
加入《认识自己》互助学习！
        还差最后1人加入，免费解锁前7天课程
         {
                window.location.href = `/pay/static/camp/group?groupCode=${groupCode}&share=true`
              }}>邀请更多好友加入

    )
  }

}
<file_sep>/src/modules/customer/person/async.ts

import requestProxy from '../../../components/requestproxy/requestProxy'

export function loadCertificates() {
  return requestProxy.getProxy('/rise/customer/get/certificate')
}

export function loadAllElites() {
  return requestProxy.getProxy('/rise/school/friend/load/all')
}

<file_sep>/src/modules/home/livelist/LiveListPage.tsx
import * as React from 'react'
import './LiveListPage.less'
import { LiveHome } from '../components/live/LiveHome'
import { changeTitle } from '../../../utils/helpers'
import { loadAllLives } from '../async'
import { connect } from 'react-redux'
import { startLoad, endLoad, alertMsg, set } from 'reduxutil/actions'
import { mark } from '../../../utils/request'
import { connect } from 'react-redux'

@connect(state => state)
export default class LiveListPage extends React.Component {

  constructor () {
    super()
    this.state = {
      data: [],
    }
  }

  async componentWillMount () {
    mark({ module: '打点', function: '着陆二级页', action: '打开直播列表页' })
    changeTitle('拓眼界')
    let res = await loadAllLives()
    if (res.code === 200) {
      this.setState({
        data: res.msg,
      })
    } else {
      dispatch(alertMsg(res.msg))
    }
  }

  render () {
    return (

        {
          this.state.data.map((item, index) => {
            return (
              <LiveHome data={item} key={index}/>
            )
          })
        }

    )
  }

}
<file_sep>/src/modules/home/components/activity/ActivityHome.tsx
import * as React from 'react'
import './ActivityHome.less'
import AssetImg from '../../../../components/AssetImg'
import { calcScheduleData } from '../../../schedule/overview/util'
import { splitContentWithSuffix } from '../../../../utils/helpers'
import { mark } from '../../../../utils/request'
import { connect } from 'react-redux'
import { startLoad, endLoad, alertMsg, set } from 'reduxutil/actions'

interface ActivityHomeProps {
  data: any
}

@connect(state => state)
export class ActivityHome extends React.Component<ActivityHomeProps, any> {

  constructor () {
    super()
    this.state = {
      data: {},
    }
  }

  STATUS = {
    PREPARE: 1,
    CLOSED: 2,
    REVIEW: 3,
  }

  componentWillMount () {
    this.setState({
      data: this.props.data,
    })
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired,
  }

  handleClick (targetUrl) {
    const { dispatch } = this.props
    mark({ module: '打点', function: '着陆页', action: '点击活动' })
    if (targetUrl) {
      window.location.href = targetUrl
    } else {
      dispatch(alertMsg('报名已经结束咯，敬请期待下次活动'))
    }
  }

  render () {
    const {
      name = '',
      holder = '',
      startTimeStr = '',
      location = '',
      status = 1,
      thumbnail = '',
      targetUrl = '',
    } = this.state.data

    const renderStatus = () => {
      switch (status) {
        case this.STATUS.PREPARE:
          return (

活动报名

          )
          break
        case this.STATUS.CLOSED:
          return (

活动结束

          )
          break
        case this.STATUS.REVIEW:
          return (

活动回顾

          )
          break
        default:
          return

          break
      }
    }

    return (

 this.handleClick(targetUrl)}>

{splitContentWithSuffix(name, 9)}

举办人：{holder}

举办时间：{startTimeStr}

举办地点：{splitContentWithSuffix(location, 10)}

        {renderStatus()}
        <AssetImg className="thumbnail" url={thumbnail}/>

    )
  }

}
<file_sep>/src/modules/practice/challenge/Challenge.tsx
import * as React from 'react'
import { connect } from 'react-redux'
import './Challenge.less'
import { loadChallengePractice, submitChallengePractice } from './async'
import { startLoad, endLoad, alertMsg, set } from '../../../redux/actions'
import Work from '../components/NewWork'
import Editor from '../../../components/simditor/Editor'
import { merge } from 'lodash'
import { mark } from '../../../utils/request'
import AssetImg from '../../../components/AssetImg'
import { ColumnSpan } from '../../../components/ColumnSpan'
import { Block } from '../../../components/Block'
import { ProblemTitle } from '../../problem/components/ProblemTitle'
import { FooterButton } from '../../../components/submitbutton/FooterButton'

@connect(state => state)
export default class Challenge extends React.Component <any, any> {
  constructor() {
    super()
    this.state = {
      data: {},
      knowledge: {},
      submitId: 0,
      page: 1,
      otherList: [],
      opacity: 0
    }
  }

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  componentWillMount() {
    mark({ module: '打点', function: '学习', action: '打开小目标页' })

    const { dispatch, location } = this.props
    dispatch(startLoad())
    loadChallengePractice(location.query.id, location.query.planId).then(res => {
      dispatch(endLoad())
      const { code, msg } = res
      if(code === 200) {
        const { content } = msg
        this.setState({ data: msg, submitId: msg.submitId, planId: msg.planId, edit: !content })
      }
      else dispatch(alertMsg(msg))
    }).catch(ex => {
      dispatch(endLoad())
      dispatch(alertMsg(ex))
    })
  }

  onEdit() {
    this.setState({ edit: true })
  }

  goComment(submitId) {
    this.context.router.push({
      pathname: '/rise/static/practice/challenge/comment',
      query: merge({ submitId: submitId }, this.props.location.query)
    })
  }

  onSubmit() {
    mark({ module: '打点', function: '小目标', action: '提交小目标' })
    const { dispatch, location } = this.props
    const { data, planId } = this.state
    const { complete, practicePlanId } = location.query
    const answer = this.refs.editor.getValue()
    if(answer == null || answer.length === 0) {
      dispatch(alertMsg('请填写作业'))
      return
    }
    this.setState({ showDisable: true })
    submitChallengePractice(planId, location.query.id, { answer }).then(res => {
      const { code, msg } = res
      if(code === 200) {
        dispatch(startLoad())
        if(complete == 'false') {
          dispatch(set('completePracticePlanId', practicePlanId))
        }
        loadChallengePractice(location.query.id, location.query.planId).then(res => {
          dispatch(endLoad())
          const { code, msg } = res
          if(code === 200) {
            this.setState({ data: msg, submitId: msg.submitId, planId: msg.planId, edit: false })
          }
          else dispatch(alertMsg(msg))
        })
        this.setState({ showDisable: false })
      }
      else {
        dispatch(alertMsg(msg))
        this.setState({ showDisable: false })
      }
    }).catch(ex => {
      dispatch(endLoad())
      dispatch(alertMsg(ex))
      this.setState({ showDisable: false })
    })
  }

  render() {
    const { data, edit = false, showDisable } = this.state
    const { content } = data
    const { planId } = this.props.location.query

    return (
      <Block>
        <ProblemTitle problemId={this.props.location.query.id}/>

小目标

            你有什么目标，可以利用本课程的训练实现呢？制定目标帮你更积极地学习，也带给你更多成就感！

            <AssetImg className='challenge-image'
                      url="https://static.iqycamp.com/images/fragment/challenge_practice_2.png"/>

小提示

本题答案仅自己可见

目标最好是某个具体问题或场景

制定目标之前，可以先回顾该课程的知识体系

          <ColumnSpan style={{ margin: '0 -3rem' }}/>

          {
            edit ?
              <Block>

更喜欢电脑上提交?

登录www.iquanwai.com/community

                <Editor ref="editor" moduleId={3} value={content}
                        placeholder="有灵感时马上记录在这里吧，系统会自动为你保存。"/>
                {
                  showDisable ?
                    <FooterButton btnArray={[{text: '提交中'}]}/> :
                    <FooterButton btnArray={[{click:() => this.onSubmit(), text:'提交'}]}/>
                }
              </Block> :
              <Block>

我的目标

                <Work onEdit={() => this.onEdit()} operation={false}
                      headImage={window.ENV.headImgUrl} userName={window.ENV.userName} {...data}/>
                <FooterButton btnArray={[{
                  click: () => this.context.router.push({pathname:'/rise/static/plan/study', query:{planId}}),
                  text: '返回'}]}/>
              </Block>
          }

      </Block>
    )
  }
}
<file_sep>/src/modules/forum/commons/DialogBottom/DialogBottomBtn.less
@import "../../variable.less";

// DialogBottomBtn
.dialog-bottom-btn {
  position: relative;
  padding-top: 15px;
  padding-bottom: 40px;

  .dialog-bottom-desc {
    width: 20%;
    font-size: 1.1em;
    color: @color_strong;
    height: 100%;
  }

  .dialog-bottom-btn-area {
    position: absolute;
    right: 0;
    .btn1-right-content {
      height: 23px;
      min-width:4em;
      color: @color_orange;
      border: 1px solid @color_orange;
      display: inline-block;
      font-size: @font_click;
      text-align: center;
      line-height: 23px;
      border-radius: 5px;
      padding: 0 5px;
      margin-right: 25px;
      &.disable {
        color: @color_help;
        border: 1px solid @color_help;
      }
    }
    .btn2-right-content {
      height: 23px;
      min-width:4em;
      color: @color_orange;
      border: 1px solid @color_orange;
      background-color: #FFF;
      display: inline-block;
      font-size: @font_click;
      text-align: center;
      line-height: 23px;
      border-radius: 5px;
      padding: 0 5px;
      &.disable {

        color: #FFF;
        border: 1px solid @color_orange;
        background-color: @color_orange;
      }
    }

  }

}
<file_sep>/src/modules/home/components/live/LiveHome.less
.live-home-component {
  box-sizing: border-box;
  position: relative;
  width: 100%;
  height: 10rem;
  margin: 2rem 0 4rem;
  .title {
    position: absolute;
    top: 0;
    left: 0;
    font-size: 1.7rem;
    color: #333333;
  }
  .speaker {
    position: absolute;
    left: 0;
    top: 3.5rem;
    font-size: 1.3rem;
    color: #333333;
  }
  .speaker-desc {
    position: absolute;
    left: 0;
    top: 6rem;
    font-size: 1.2rem;
    color: #666666;
  }
  .time {
    position: absolute;
    left: 0;
    bottom: 0;
    font-size: 1.2rem;
    color: #666666;
    &.hot {
      color: #ff983f;
    }
  }
  .thumbnail {
    position: absolute;
    width: 10rem;
    height: 10rem;
    right: 0;
    top: 0;
    border-radius: 4px;
  }
}

languages: JavaScript TypeScript Less TSX

repo_name: lance-li/platon-static