基于react的影院购票应用

写在最前

此次使用react&redux,来模拟了一个购票app,须要关注的是本次所有数据均为mock实现,不涉及后台。同时其中不会涉及react与redux的语法,只关注到一些模拟原生效果的实现理念。没有接触过react的童鞋们能够关注下阮一峰老师的react入门教程,至于redux,redux中文文档上面也有着详细的说明。不过做者对redux也很感兴趣,打算学习一波源码后(若是整个明白了),可能也会出一个分享,届时欢迎前来交流~ #github地址,捂脸求starjavascript

部署

本应用所有运行在开发模式下,开启了devserver,没有进行过生产环境测试,若是出现问题你们能够留言~css

git clone https://github.com/Aaaaaaaty/react_movie 

cd react_movie 

cnpm i || npm i

将./data及./src/images 文件 拷贝进dist //项目依赖的图片及假数据

npm start复制代码

重点实现 —— 一个电影选座组件

本次分享的重点是一个基于react的选座组件demo。做者在开发这个组件的时候有观察过微信和支付宝内嵌的影院选座功能。可是无奈看不到代码,一切纯平臆想,说错勿喷。我的感受微信里面外包的微票儿内的选座模块里面的手势功能为原生浏览器自带的缩放,那么控制上会相对粗暴,缩放上面相对没有支付宝精细。而支付宝上面不只缩放手感好同时包含了左上方小窗预览功能,可谓用户体验良好(我不是阿里脑残粉hhhhh,虽然事实如此?),因此做者并无感受出来这个是混合开发的组件仍是原生的仍是什么的。。。好了bb了半天,如今轮到做者本身来实现一个了。html

效果图

选座组件

很惋惜chrome的模拟器下没法演示手势的操做。其实这里面实现了缩放功能,以及在选座界面放大的时候左侧上方的预览图中的红色标示线则会相应的缩小来指出你选中的范围在整个影院中的位置。此次做者使用了react来书写这个组件,全部的移动缩放所有经过js计算,在真机测试中页面会有些许卡顿。不过做者相信若是进行防抖和节流的优化,在手机浏览器中的体验应该能够更优秀一些。java

核心思路

  1. 按照后端接口mock数据
  2. 渲染座位
  3. 增长手势操做
  4. 管理选座信息
  5. 渲染预览小图

mock数据

// ./dist/data/filmSeat.json
{
  "seatId":"0000002-1-1",
  "rowId": 1, //行index
  "columnId": 1, //列index
  "xAxis":3, //行绝对定位
  "yAxis":1, //列绝对定位
  ...
  "isSold":false //是否卖出(用于渲染座位颜色)
}复制代码

在这里须要注意的是:行和列的index值与其绝对定位的区别。咱们在电影院中座位摆放的地理位置是千奇百怪的,可是索引序号必定是从1到X。从而就有了如上的四个属性。在渲染座位布局的时候必定是采用xAxis & yAxis才能达到展现影厅座位排布的效果。若是还有点懵请看上图的演示中的座位的排布。react

渲染座位

在这里咱们先假设要渲染一个占设备视口80%宽的区域来摆放咱们的座椅。那么由此就会有一个问题就是咱们不肯定座椅的数量。故座椅的宽是不能定死的(方便起见,让座椅为正方形,宽高相等),即宽度应为 视口宽*80% / 座椅数量git

固然若是座椅太少那么就会致使宽太大这种状况这些极端条件若是有兴趣能够后期再进行判断github

// ./src/Components/FilmSeat/FilmSeat.js
let list = seatList.map((item, index) => {
    let style = {
      position: 'absolute',
      left: `${seatWidth * item.xAxis + seatWidth / 2 }rem`,
      top: `${seatWidth * item.yAxis}rem`, // 根据数据中的绝对定位来动态渲染座位位置
      width: `${seatWidth}rem`
    }
    return (
      <img  key={ 'seatId' + index }
            style={ style }
            src={ `.\/images\/${isSoldUrl[index]}.png` }
            onTouchTap={ this.changeSeat.bind(this, isSoldUrl, index, item) }
            className={ styles.seatItem }></img> // 每一个座位都是一张小图
    )
})复制代码

手势操做

// ./src/Components/FilmSeat/FilmSeat.js
<div  ...
      onTouchStart={ this.onTouchStart.bind(this) }
      onTouchMove={ this.onTouchMove.bind(this) }
      onTouchEnd={ this.onTouchEnd.bind(this) }>复制代码

对于手势操做,采用了浏览器的三个原生触摸事件。下面主要说明如何使用react实现一个原生的拖拽效果:chrome

// ./src/Components/FilmSeat/FilmSeat.js
onTouchStart(e) { //三个事件均会传入event事件
    e.preventDefault()
    let { left, top... } = this.state
    ...
    if(e.touches.length === 1) { //判断是否为一个手指触摸
      let startX = e.touches[0].clientX //获得起始横坐标
      let startY = e.touches[0].clientY //获得起始纵坐标
      state = {
        startX: startX,
        startY: startY,
        lastDisX: left, //记录上一次横轴偏移量
        lastDisY: top, //记录上一次纵轴偏移量
        ...
      }
    } 
    ...
    this.setState(state)
  }
 onTouchMove(e) {
    e.preventDefault()
    let { startX, startY ... } = this.state
      if(e.touches.length === 1) {
        let moveX = e.touches[0].clientX //记录当前的位置
        let moveY = e.touches[0].clientY
        let disX = moveX - startX + lastDisX //记录如今手指相对屏幕左侧距离
        let disY = moveY - startY + lastDisY
        ...
        this.setState({
          moveX: moveX,
          moveY: moveY,
          left: disX,
          top: disY,
        })
      } else if(e.touches.length === 2) {
        ...
      }
  }
  onTouchEnd(e) {
    e.preventDefault()
    ...
    //主要作一些拖拽完成以后的判断,重置初始值等等
  }复制代码

总结来讲核心思路是,e.touches[0].clientX/Y能够提供手指在屏幕中的绝对距离,咱们滑动中能够记录到滑动了的相对距离。那么在下次滑动前就须要记录下上一次的相对距离,下次滑动时就要加上上次的距离。否则每次从新拖拽就会从0,0点从新开始。npm

管理选座信息

经过效果图咱们能够知道,在组件中同时须要渲染座位的选取,下方弹出/关闭座位信息等效果。虽然效果多样可是基本能够看为两个状态即座位是否选中,这就使用到了redux来做为状态管理。经过redux来抽象出公共状态,让不一样的效果渲染都基于同一个状态,从而达到效果联动。json

// ./src/Container/FilmChooseSeat.js
changeSeatConf(item, isSoldUrl, type) {
    const { changeFilmBuySeatList } = this.props // 拿到store中传出来的方法
    let data = {
      item: item, //座位信息
      isSoldUrl: isSoldUrl, //全部座位颜色列表
      type: type
    }
    changeFilmBuySeatList(data)
  }
  render() {
    let { filmSeatList, filmBuyList, location } = this.props
    ...
    return  (
              <div>
                <FilmSeatTitle location={ location }/>
                <FilmSeat filmSeatList={ filmSeatList } //选座拖拽区域
                          filmBuyList={ filmBuyList }
                          animationTime={ 200 }
                          changeSeatConf={ this.changeSeatConf.bind(this) }/> 
                          //经过这个函数将组件中事件传递到container中,
                          //由container发起action来进行改变state
                <FilmSeatSale filmBuyList= { filmBuyList } //选座信息
                              filmSeatList={ filmSeatList }
                              changeSeatConf={ this.changeSeatConf.bind(this) }/>
              </div>
            )
  }
// ./src/Redux/Store/Store.js
export const mapStateToProps =(state)=> {
  return {
    ...
    filmSeatList:state.filmChooseSeatReducer.filmSeatList,//电影座位列表
    filmBuyList:state.filmChooseSeatReducer.filmBuyList,//电影选座列表
  }
}
export const mapDispatchToProps=(dispatch)=> {
  return {
    ...
    getFilmSeatList:(url,data)=>dispatch(FilmChooseSeatActions.fetchFilmSeatList(url,data)),//获取电影座位列表
    changeFilmBuySeatList:(data)=>dispatch(FilmChooseSeatActions.changeFilmBuySeatList(data))//选中座位购票
  }
}复制代码

发起action后,在reducer中改变维护的filmBuyList数组状态,就能够同时渲染好整个界面的变化。

// ./src/Redux/Reducer/FilmChooseSeatReducer.js
export const filmBuyList = (state = {item:[],isSoldUrl:{},type:''}, action={})=>{
    switch(action.type){
        case FilmChooseSeatActions.CHANGE_FILM_BUYSEAT:
        let _state = Object.assign({}, state)
        if(action.text.type === 'add') {
          _state.item.push(action.text.item)
        } else {
          let index = _state.item.indexOf(action.text.item)
          _state.item.splice(index, 1)
        }
        _state.isSoldUrl = action.text.isSoldUrl
        _state.type = action.text.type
        return _state
        default:
        return state
    }
}复制代码

渲染预览小图

当完成了大图的渲染以及选座状态切换的工做以后,只须要复制一份大图的渲染的那段jsx修改css样式就能够完成一个预览小图。在这期间你不须要作任何事就能够看到小图上面一样会存在选座状态的切换,这就是状态管理的好处。只要你的界面效果和状态进行了绑定,那么在以后的工做中你就不须要再去关注效果而只须要关注状态是否正确便可。在这其中惟一有一点问题的地方是预览图中红色提示框的缩放和大图的缩放是成反比的。大图放大预览图中的红色框应该缩小,同时大图可拖拽的范围应该和红框的移动范围有一个比例系数。在此次的实现中做者用了scaleNum这个状态来控制其缩放的系数,有兴趣的童鞋能够本身尝试一下如何计算一个正确的系数来保证大图和预览图缩放后红框移动距离和大图拖拽范围的匹配。

其余功能组件

区域选择组件

区域选择

电影列表组件

电影列表

电影详情组件

电影详情

电影排期组件

电影排期

再次广告github地址,欢迎你们一块儿交流~~~#另附做者blog仓库,不按期更新

相关文章
相关标签/搜索