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 = {
width: `${SEAT_WIDTH}px`,
height: `${SEAT_WIDTH}px`,
margin: `${SEAT_MARGIN}px`,
cursor: 'pointer',
}; // 坐标轴样式
const axisStyle = {
width: `${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 = {
width: `${(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等,由于是大神编写的,所以在理解上我都花了很大功夫,不过这样的代码才有助于成长,特此记录一下。

React实现座位排布组件的更多相关文章

  1. CSS布局之div交叉排布与底部对齐--flex实现

    最近在用wordpress写页面时,设计师给出了一种网页排布图样,之前从未遇到过,其在电脑上(分辨率大于768px)的效果图如下: 而在手机(分辨率小于等于768px)上要求这样排列: 我想到了两种方 ...

  2. React 深入系列1:React 中的元素、组件、实例和节点

    文:徐超,<React进阶之路>作者 授权发布,转载请注明作者及出处 React 深入系列,深入讲解了React中的重点概念.特性和模式等,旨在帮助大家加深对React的理解,以及在项目中 ...

  3. React 深入系列2:组件分类

    文:徐超,<React进阶之路>作者 授权发布,转载请注明作者及出处 React 深入系列2:组件分类 React 深入系列,深入讲解了React中的重点概念.特性和模式等,旨在帮助大家加 ...

  4. React 深入系列4:组件的生命周期

    文:徐超,<React进阶之路>作者 授权发布,转载请注明作者及出处 React 深入系列4:组件的生命周期 React 深入系列,深入讲解了React中的重点概念.特性和模式等,旨在帮助 ...

  5. 按照excel文档中的内容在当前cad图纸中自动排布实体

    本例实现的主要功能是读取excel文档中的内容,其次是将按照读取的信息在当前cad图纸中添加相应的实体.下面先介绍实现代码: CString excelPath; //外部excel文档的地址 Upd ...

  6. React(17)异步组件

    26.异步组件当在React里使用异步组件时,核心知识是两个: webpack 如何异步加载其他模块:通过 require(['xxx'], function(module){})来实现:React ...

  7. ElementUI(vue UI库)、iView(vue UI库)、ant design(react UI库)中组件的区别

    ElementUI(vue UI库).iView(vue UI库).ant design(react UI库)中组件的区别: 事项 ElementUI iView ant design 全局加载进度条 ...

  8. 实现LinearLayout(垂直布局,Gravity内容排布)

    首先上Gravity的代码,Android原版的Gravity搞得挺复杂的,太高端了.但基本思路是使用位运算来做常量,我就自己消化了一些,按自己的思路来实现. 先上代码,在做分析. package k ...

  9. React中的高阶组件,无状态组件,PureComponent

    1. 高阶组件 React中的高阶组件是一个函数,不是一个组件. 函数的入参有一个React组件和一些参数,返回值是一个包装后的React组件.相当于将输入的React组件进行了一些增强.React的 ...

随机推荐

  1. 解决ubuntu的Idea启动No JDK found. Please validate either IDEA_JDK, JDK_HOME or JAVA_HOME environment variable points to valid JDK installation.

    直接在idea安装目录下运行idea.sh可以正常启动,但是使用ubuntu的dash搜索出来的idea报错,No JDK found. Please validate either IDEA_JDK ...

  2. codeforces1249-div3

    A B C 等比数列的性质,前面的i项的和,不会超过第i+1项 D 有若干个区间,要求每一个点被区间覆盖的次数不能超过k个.问移除的最少的区间的数目. 贪心: 若某个点被覆盖了k次以上,那么肯定是移除 ...

  3. Porject Euler Problem 6-Sum square difference

    我的做法就是暴力,1+...+n 用前n项和公式就行 1^2+2^2+....+n^2就暴力了 做完后在讨论版发现两个有趣的东西. 一个是 (1+2+3+...+n)^2=(1^3)+(2^3)+(3 ...

  4. Python--day68--ORM内容回顾

    Django项目如何使用ORM连接MySQL: 多对多关系讲解:

  5. UVA 3027 Corporative Network 带权并查集、

    题意:一个企业要去收购一些公司把,使的每个企业之间互联,刚开始每个公司互相独立 给出n个公司,两种操作 E I:询问I到I它连接点最后一个公司的距离 I I J:将I公司指向J公司,也就是J公司是I公 ...

  6. 2019-9-9-dotnet-获取本机-IP-地址方法

    title author date CreateTime categories dotnet 获取本机 IP 地址方法 lindexi 2019-09-09 15:56:33 +0800 2019-0 ...

  7. java StringBuffer 与 StringBuilder

    String是不可变类,一旦String对象被创建,包含在对象中的字符序列是不可变的,直到对象被销毁: StringBuffer 与 StringBuilder对象则是可变的! 举例说明这两个的好处: ...

  8. H3C 什么是路由

  9. 2018-8-10-WPF-程序生成类库错误

    title author date CreateTime categories WPF 程序生成类库错误 lindexi 2018-08-10 19:16:53 +0800 2018-2-13 17: ...

  10. Jasypt加密SpringBoot配置文件

    如果 SpringBoot 的 properties 文件中含有用户名密码等敏感信息,为了安全起见需要对明文密码加密.Jasypt 是用来加密的 jar 包. 1.引入 Jasypt 在 pom.xm ...