zoukankan      html  css  js  c++  java
  • React实现座位排布组件

    React实现座位排布组件

    最近在开发一个影院系统的后台管理系统,该后台可以设置一个影厅的布局。
    后台使用的是react框架,一位大神学长在几天之内就把这个控件研究出来了,并进行了较为严密的封装,佩服不已,虽然不是我写的,但着实有必要学习和记录一下。

    以下是全部代码:

    const MAX_COLUMN = 50;
    const DEFAULT_COLUMN = 25;
    
    const MAX_ROW = 50;
    const DEFAULT_ROW = 12;
    
    const SEAT_WIDTH = 15; // 每一个小座位的宽度
    const SEAT_MARGIN = 2; // 每一个小座位的边距
    
    const SEAT_BOUNDS_MARGIN_LEFT = 10; // seat_bounds_margin_left
    
    // 头部信息(提示信息)
    
    // 根据传入的座位的state属性,展示该座位图片的颜色(已选:灰色,可选:白色,故障:红色,无座位:白色空白)
    const getSeatImg = seat => {
      switch (seat.state) {
        case SEAT_STATE.IDLE:
          return WHITE_SEAT;
        case SEAT_STATE.SELECTED:
          return GREEN_SEAT;
        case SEAT_STATE.LOCKED:
          return RED_SEAT;
        default:
          return '';
      }
    };
    
    // 格式化返回座位信息,例如:seat_1_2
    const getSeatKey = seat => {
      return `seat_${seat.xAxis}_${seat.yAxis}`;
    };
    
    // 每一个小座位的样式
    const seatStyle = {
       `${SEAT_WIDTH}px`,
      height: `${SEAT_WIDTH}px`,
      margin: `${SEAT_MARGIN}px`,
      cursor: 'pointer',
    };
    
    // 坐标轴样式
    const axisStyle = {
       `${SEAT_WIDTH + SEAT_MARGIN * 2}px`,
      height: `${SEAT_WIDTH + SEAT_MARGIN * 2}px`,
      backgroundColor: 'grey',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
    };
    
    // 设置座位样式,需要传入onClick方法
    const SeatIcon = ({ seat, onClick, editMode }) => {
      if (seat.state === SEAT_STATE.NULL) {
        return (
          <div
            style={{
              ...seatStyle,
              backgroundColor: editMode ? '#dcdcdc' : '#ffffff',
            }}
            onClick={onClick}
          />
        );
      }
      return <img style={seatStyle} src={getSeatImg(seat)} alt="" onClick={onClick} />;
    };
    
    class EditCinemaSeats extends React.Component {
      constructor(props) {
        super(props);
        // colum: 35,row:15
        const { column, row, seats } = props;
        console.log(`constructor, seats,`, seats);
        this.handlerSeatColumnRowChanged({ column, row, seats }, true);
      }
    
      componentWillReceiveProps(nextProps) {
        if (
          (isEmpty(this.props.seats) && !isEmpty(nextProps.seats)) ||
          this.props.row !== nextProps.row ||
          this.props.column !== nextProps.column
        ) {
          this.handlerSeatColumnRowChanged(nextProps);
        }
      }
    
      notifySeatsChange = () => {
        const { row, column, seats } = this.state;
        // 从整个布局中筛选除可选的座位,拼接成列表到selectedSeats中
        const selectedSeats = chain(seats)
          .filter(it => it.state === SEAT_STATE.IDLE)
          .value();
        this.props.onSeatsChange({
          row,
          column,
          selectedSeats,
        });
      };
    
      // 单击控制该座位可选还是不存在(针对影厅排布局的时候使用)
      handlerSeatClick = seat => {
        const { seats } = this.state;
        seats.forEach(item => {
          if (getSeatKey(item) === getSeatKey(seat)) {
            if (item.state !== SEAT_STATE.IDLE) {
              item.state = SEAT_STATE.IDLE;
            } else {
              item.state = SEAT_STATE.NULL;
            }
          }
        });
        this.setState({ seats }, this.notifySeatsChange);
      };
    
      handlerSeatColumnRowChanged = ({ row, column, seats }, initial) => {
        console.log("+++++++++++");
        console.log(seats);
        const seatMap = reduce(
          seats,
          (obj, seat) => {
            obj[getSeatKey(seat)] = seat;
            return obj;
          },
          {}
        );
        const newList = createSeats(0, column, 0, row);
        newList.forEach(it => {
          const pre = seatMap[getSeatKey(it)];
          if (!isNil(pre) && pre.state === SEAT_STATE.IDLE) {
            it.state = SEAT_STATE.IDLE;
          }
        });
        if (initial) {
          this.state = {
            row,
            column,
            seats: newList,
          };
          this.notifySeatsChange();
        } else {
          this.setState(
            {
              row,
              column,
              seats: newList,
            },
            this.notifySeatsChange
          );
        }
      };
    
      renderEditHeader = () => {
        const { column, row, editMode } = this.props;
    
        return (
          <div className={styles.cinema_seats_header_1}>
            <img src={WHITE_SEAT} alt="" />
            <div style={{ marginLeft: '5px' }}>已选</div>
            <InputNumber
              style={{ marginLeft: '15px' }}
              min={1}
              disabled={!editMode}
              max={MAX_COLUMN}
              defaultValue={column}
              formatter={value => `y:${value}`}
              parser={value => value.replace('y:', '')}
              value={this.state.column}
              onChange={value => {
                this.handlerSeatColumnRowChanged({
                  ...this.state,
                  column: value,
                });
              }}
            />
            <InputNumber
              style={{ marginLeft: '15px' }}
              min={1}
              disabled={!editMode}
              max={MAX_ROW}
              defaultValue={row}
              formatter={value => `x:${value}`}
              parser={value => value.replace('x:', '')}
              value={this.state.row}
              onChange={value => {
                this.handlerSeatColumnRowChanged({
                  ...this.state,
                  row: value,
                });
              }}
            />
          </div>
        );
      };
    
      renderXAxis = () => {
        const { column } = this.state;
        const children = [];
        for (let i = 0; i < column; i++) {
          children.push(
            <div key={`xAxis_${i}`} style={axisStyle}>
              <span style={{ color: '#ffffff', fontSize: '8px' }}>{i + 1}</span>
            </div>
          );
        }
        return (
          <div
            style={{
              display: 'flex',
              flexDirection: 'row',
              marginLeft: SEAT_BOUNDS_MARGIN_LEFT + SEAT_WIDTH,
              marginTop: '10px',
            }}
          >
            {children}
          </div>
        );
      };
    
      renderYAxis = () => {
        const { row } = this.state;
        const children = [];
        for (let i = 0; i < row; i++) {
          children.push(
            <div key={`yAxis_${i}`} style={axisStyle}>
              <span style={{ color: '#ffffff', fontSize: '8px' }}>{i + 1}</span>
            </div>
          );
        }
        return (
          <div
            style={{
              display: 'flex',
              flexDirection: 'column',
              marginTop: '10px',
            }}
          >
            {children}
          </div>
        );
      };
    
      // 显示整个排座的列表
      renderSeatsBounds = () => {
        const { editMode } = this.props;
        const { row, column } = this.state;
        const boxStyle = {
           `${(SEAT_WIDTH + SEAT_MARGIN * 2) * column}px`,
          height: `${(SEAT_WIDTH + SEAT_MARGIN * 2) * row}px`,
          marginTop: '10px',
        };
        const { seats } = this.state;
        return (
          <div style={boxStyle} className={styles.cinema_seats_bounds}>
            {seats.map(item => (
              <SeatIcon
                key={getSeatKey(item)}
                seat={item}
                editMode={editMode}
                onClick={() => {
                  if (editMode) {
                    this.handlerSeatClick(item);
                  }
                }}
              />
            ))}
          </div>
        );
      };
    
      render() {
        return (
          <div className={styles.cinema_seats_container}>
            {this.renderEditHeader()}
            {this.renderXAxis()}
            <div
              style={{
                display: 'flex',
                flexDirection: 'row',
              }}
            >
              {this.renderYAxis()}
              <div
                className={styles.cinema_seats_container_box}
                style={{
                  marginLeft: SEAT_BOUNDS_MARGIN_LEFT,
                }}
              >
                {this.renderSeatsBounds()}
              </div>
            </div>
          </div>
        );
      }
    }
    
    EditCinemaSeats.propTypes = {
      seats: PropTypes.array,
      column: PropTypes.number,
      row: PropTypes.number,
      onSeatsChange: PropTypes.func,
      editMode: PropTypes.bool,
    };
    
    EditCinemaSeats.defaultProps = {
      seats: createDefaultSeat(), // 初始值为中间一块为可选状态的区域
      row: DEFAULT_ROW,
      column: DEFAULT_COLUMN,
      onSeatsChange: noop,
      editMode: true,
    };
    
    // 创建默认座位
    function createDefaultSeat() {
      const seats = createSeats(1, 24, 0, 11);
      seats.forEach(it => {
        it.state = SEAT_STATE.IDLE;
      });
      // const res = chain(seats).filter((item) => item.yAxis !== 3).value()
      return seats;
    }
    
    // 先设置矩形座位区域,初始值都为null,即:不存在座位
    function createSeats(fromX, toX, fromY, toY) {
      const seats = [];
      for (let y = fromY; y < toY; y++) {
        for (let x = fromX; x < toX; x++) {
          seats.push({
            xAxis: x,
            yAxis: y,
            state: SEAT_STATE.NULL,
          });
        }
      }
      return seats;
    }
    
    const SEAT_STATE = {
      NULL: 0, // 空,没位置
      IDLE: 1, //可选
      SELECTED: 2, // 已选
      LOCKED: 3, //坏了
    };
    
    export default EditCinemaSeats;
    

    除了图标资源以外,基本上所有的代码都在这了,注意代码中用到了lodash库中的一些方法,比如chain和reduce等,由于是大神编写的,所以在理解上我都花了很大功夫,不过这样的代码才有助于成长,特此记录一下。

  • 相关阅读:
    如何使用 ADO 将数据从 ADO 数据源传输到 Excel
    C#2.0中的可空类型Nullable
    2007年你必须学习的10项.NET技术
    日期正则表达式
    只允许n个实例进行
    MFC程序隐藏任务栏图标的三种方法
    Qt4小技巧——将button布局在QToolbar的右边
    QT学习随笔20120813
    只允许一个实例,允许n个实例
    折腾的DVCS
  • 原文地址:https://www.cnblogs.com/tian874540961/p/10519696.html
Copyright © 2011-2022 走看看