界面展示
主要使用到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;