Bootstrap

影院座位选择简易实现(uniapp)

界面展示

主要使用到uniap中的movable-area,和movable-view组件实现。

代码逻辑分析

1、使用movable-area和movea-view组件,用于座位展示

<div class="ui-seat__box">
	<movable-area  class="ui-movableArea">
		<movable-view></movable-view>
	</movable-area>
</div>
.ui-movableArea {
		width: 600rpx;
		height: 500rpx;
		border: 1rpx solid #999;
		overflow: hidden;
	}

先给movable-area组件定义宽高,用于展示区域

2、 给moveable-view设置可移动,宽高等

<movable-area class="ui-movableArea">
				<movable-view direction="all" :out-of-bounds="false" :scale="false" class="ui-movableView"
					:style="{width:60 * whData.width + 'px',height:40 * whData.height + 'px'}" @change="handleMove"
					scale-max="1.5"
					damping="200"
					@scale="handleSize">

				</movable-view>
			</movable-area>

其中direction,out-of-bounds,scale,scale-max,damping,@scale等配置项在uniapp文档中查看介绍,分别表示为(是否全方向移动,超出能否移动,能否放大,最大的放大倍数,回弹时间,移动的回调)

3、影院的座位数据

通过以下方法,随机生成id唯一的座位,用于座位展示

function generateSeatArray(count) {
  const seats = [];
  for (let i = 0; i < count; i++) {
    seats.push({
      id: i + 1, // 随机生成 1 到 100 之间的唯一ID
      seat_x: (i % 11) + 1, // 规律递增 seat_x,范围 1 到 11
      seat_y: Math.floor(i / 11) + 1, // 规律递增 seat_y,范围 1 到 11
      canBuy: Math.random() > 0.5, // 随机生成 true 或 false
      price: Math.floor(Math.random() * 81) + 20 // 随机生成 20 到 100 之间的整数
    });
  }
  return seats;
}
export default generateSeatArray;

此方法结果为对象数组,表示以左上角为原点,右边为x轴正方向,下面为y轴正方向的坐标(seat_x,seat_y),canBuy表示能否购买此座位,price表示座位价格

[
    {
		seat_x: 1,
		seat_y: 1,
		canBuy: true,
		price: 20
	},
    {
		seat_x: 2,
		seat_y: 1,
		canBuy: true,
		price: 20
	},
    ........
]

4、moveable-view的宽高设置

回到标题2的代码片段

<movable-area class="ui-movableArea">
				<movable-view direction="all" :out-of-bounds="false" :scale="false" class="ui-movableView"
					:style="{width:60 * whData.width + 'px',height:40 * whData.height + 'px'}" @change="handleMove"
					scale-max="1.5"
					damping="200"
					@scale="handleSize">

				</movable-view>
			</movable-area>

在mova-view中有 :style="{width:60 * whData.width + 'px',height:40 * whData.height + 'px'}"

whData的数据如下所示,拿到座位数据作为参数传给handleMax方法,得到最大的宽和高

handleMax(array) {
				const maxData = array.reduce((preObj, cur) => {
					preObj.width = Math.max(preObj.width, cur.seat_x);
					preObj.height = Math.max(preObj.height, cur.seat_y)
					return preObj;
				}, {
					width: 0,
					height: 0
				})
				return maxData;
			},

 datalist是标题3中生成的座位数据

this.whData = this.handleMax(dataList);
whData = {
 widht:10,
 height:10
}

5、座位展示

<movable-area class="ui-movableArea">
				<movable-view direction="all" :out-of-bounds="false" :scale="false" class="ui-movableView"
					:style="{width:60 * whData.width + 'px',height:40 * whData.height + 'px'}" @change="handleMove"
					scale-max="1.5"
					damping="200"
					@scale="handleSize">
                    <view class="ui-seat">
						<!-- 座位 -->
						<view class="ui-item" v-for="item in seatList" :key="item.id"
							:style="{top:30 * item.seat_y + 'px',left:50 * item.seat_x + 'px'}"
							@click="handleSelect(item)">
							<view class="ui-item__class__can" v-if="item.canBuy">{{item.seat_x}},{{item.seat_y}}</view>
							<view class="ui-item__class" v-else>{{item.seat_x}},{{item.seat_y}}</view>
						</view>
					</view>
				</movable-view>
			</movable-area>

在moveabel-view中新增view组件,用于展示每一个座位 ,注意看类(ui-seat)设置成相对定位

	.ui-seat {
		display: flex;
		flex-wrap: wrap;
		position: relative;
	}

类(ui-item)设置成绝对定位


	.ui-item {
		margin: 5px;
		border: #999 1px solid;
		/* padding: 20rpx; */
		position: absolute;
	}

 因为在现实情况下会出现某个地方没有座位的情况,需要使用绝对定位的方式,根据每个座位的x,y的坐标进行展示

:style="{top:30 * item.seat_y + 'px',left:50 * item.seat_x + 'px'}"

这行表示根据每个item项的坐标进行对应展示,并且不会重叠

<view class="ui-item__class__can" v-if="item.canBuy">{{item.seat_x}},{{item.seat_y}}</view>

 <view class="ui-item__class" v-else>{{item.seat_x}},{{item.seat_y}}</view></view>

关于能否选择座位使用v-if来进行判断

6、左边列表展示 

			<movable-area class="ui-movableArea">
				<movable-view direction="all" :out-of-bounds="false" :scale="false" class="ui-movableView"
					:style="{width:60 * whData.width + 'px',height:40 * whData.height + 'px'}" @change="handleMove"
					scale-max="1.5"
					damping="200"
					@scale="handleSize">
					<view class="ui-seat">
						<!-- 座位 -->
						<view class="ui-item" v-for="item in seatList" :key="item.id"
							:style="{top:30 * item.seat_y + 'px',left:50 * item.seat_x + 'px'}"
							@click="handleSelect(item)">
							<view class="ui-item__class__can" v-if="item.canBuy">{{item.seat_x}},{{item.seat_y}}</view>
							<view class="ui-item__class" v-else>{{item.seat_x}},{{item.seat_y}}</view>
						</view>
						<!-- 列表 -->
						<view class="ui-list" :style="{left: moveX + 'px'}">
							<view class="ui-list__item" v-for="i in whData.height" :key="index">
								{{i + 1}}
							</view>
						</view>
					</view>
				</movable-view>

在座位代码下面新增列表展示,在moveable-view中有@change方法,用于获取上下左右移动了多少,并存到moveX,和moveY中,根据左移的距离判断列表位置

handleMove(e) {
				const {
					x,
					y,
					source
				} = e.detail;
				setTimeout(() => {
					this.moveX = Math.abs(x);
					this.moveY = y;
				}, 200)
			},

完整代码

<template>
	<view class="content">
		<view class="ui-top">
			电影信息
		</view>
		<view class="ui-body">
			以下是座位控制
		</view>
		<div class="ui-seat__box">
			<movable-area class="ui-movableArea">
				<movable-view direction="all" :out-of-bounds="false" :scale="true" class="ui-movableView"
					:style="{width:60 * whData.width + 'px',height:40 * whData.height + 'px'}" @change="handleMove"
					scale-max="1.5"
					damping="200"
					@scale="handleSize">
					<view class="ui-seat">
						<!-- 座位 -->
						<view class="ui-item" v-for="item in seatList" :key="item.id"
							:style="{top:30 * item.seat_y + 'px',left:50 * item.seat_x + 'px'}"
							@click="handleSelect(item)">
							<view class="ui-item__class__can" v-if="item.canBuy">{{item.seat_x}},{{item.seat_y}}</view>
							<view class="ui-item__class" v-else>{{item.seat_x}},{{item.seat_y}}</view>
						</view>
						<!-- 列表 -->
						<view class="ui-list" :style="{left: moveX + 'px'}">
							<view class="ui-list__item" v-for="i in whData.height" :key="index">
								{{i + 1}}
							</view>
						</view>
					</view>
				</movable-view>
			</movable-area>
		</div>

	</view>
</template>

<script>
	import seatfun from './seat-data.js';
	export default {
		data() {
			return {
				title: 'Hello',
				seatList: [],
				whData: {},
				moveX: 0,
				moveY: 0
			}
		},
		onLoad() {
			const dataList = seatfun(50);
			// dataList.splice(2,4);
			// dataList.splice(10,12);
			this.seatList = dataList;
			// 获取宽度
			this.whData = this.handleMax(dataList);
		},
		methods: {
			handleMax(array) {
				const maxData = array.reduce((preObj, cur) => {
					preObj.width = Math.max(preObj.width, cur.seat_x);
					preObj.height = Math.max(preObj.height, cur.seat_y)
					return preObj;
				}, {
					width: 0,
					height: 0
				})
				return maxData;
			},
			handleSelect(item) {
				// console.log(item.seat_x,item.seat_y);
				if (item.canBuy) {
					item.canBuy = !item.canBuy;
				} else {
					// console.log(item);
					uni.showToast({
						title: '此座位不可选'
					})
				}
			},
			handleMove(e) {
				const {
					x,
					y,
					source
				} = e.detail;
				setTimeout(() => {
					this.moveX = Math.abs(x);
					this.moveY = y;
				}, 200)
			},
			handleSize(e){
				const {x,y,scale} = e.detail;
				setTimeout(() => {
					this.moveX = Math.abs(x);
					this.moveY = y;
				}, 200)
			}
		}
	}
</script>

<style scoped>
	view {
		box-sizing: border-box;
	}

	.ui-top {
		height: 200rpx;
		background-color: greenyellow;
	}

	.ui-movableArea {
		width: 600rpx;
		height: 500rpx;
		border: 1rpx solid #999;
		overflow: hidden;
	}

	.ui-seat__box {
		display: flex;
		justify-content: center;
	}

	.ui-seat {
		display: flex;
		flex-wrap: wrap;
		position: relative;
	}

	.ui-movableView {
		width: 700rpx;
		height: 700rpx;
		overflow: hidden;
		background-color: antiquewhite;
	}

	.ui-item__class__can {
		width: 60rpx;
		height: 40rpx;
		background-color: darkred;
	}

	.ui-item__class {
		width: 60rpx;
		height: 40rpx;
		background-color: palegreen;
	}

	.ui-item {
		margin: 5px;
		border: #999 1px solid;
		/* padding: 20rpx; */
		position: absolute;
	}

	.active {
		background-color: greenyellow;
	}

	.ui-list {
		position: absolute;
		top: 30px;
		width: 50rpx;
		background-color: #fff;
	}

	.ui-list__item {
		margin: 10rpx;
		padding: 5rpx;
	}
</style>

seat-data.js代码

function generateSeatArray(count) {
  const seats = [];
  for (let i = 0; i < count; i++) {
    seats.push({
      id: i + 1, // 随机生成 1 到 100 之间的唯一ID
      seat_x: (i % 11) + 1, // 规律递增 seat_x,范围 1 到 11
      seat_y: Math.floor(i / 11) + 1, // 规律递增 seat_y,范围 1 到 11
      canBuy: Math.random() > 0.5, // 随机生成 true 或 false
      price: Math.floor(Math.random() * 81) + 20 // 随机生成 20 到 100 之间的整数
    });
  }
  return seats;
}
export default generateSeatArray;

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;