vue影院在线选座--可拖拽、缩放--A/B/C三级座位
以前练习写项目的时候,写过一个影院的在线选座页面,也看了很多插件和别人写的,大部分是移动端,类似于美团电影那种选座,页面可以放大,缩小,拖拽,我写的是PC端的,看看效果:再放一张原图,看得清楚:说一下实现思路:1,影院座位的渲染分为三个等级,以及空白地方该怎么渲染2,左下角那个小工具栏,选择某个等级时,这个等级的座位全加上阴影凸显,取消则取消阴影,反复如此3,点击某个座位时,座位图片...
以前练习写项目的时候,写过一个影院的在线选座页面,也看了很多插件和别人写的,大部分是移动端,类似于美团电影那种选座,页面可以放大,缩小,拖拽,我写的是PC端的,看看效果:
再放一张原图,看得清楚:
说一下实现思路:
1,影院座位的渲染分为三个等级,以及空白地方该怎么渲染
2,左下角那个小工具栏,选择某个等级时,这个等级的座位全加上阴影凸显,取消则取消阴影,反复如此
3,点击某个座位时,座位图片变成已选中图片,再点则取消选中,反复如此
4,右边已选列表,要求算出用户点击了几排几号,多少钱,几个座位,取消则相应删除列表项
5,座位区域要实现缩放和拖拽
这里面比较难实现的地方就是这个渲染座位图,根据要求有几排,一排有有多少个位子,两侧的空白怎么分布的,用了一个这样的数组:
// 这个数组是座位分布图,a=A级座位,以此类推,下划线_代表空白地方
// 一横排占位32个(算上座位和下划线_占位符)
var seatOrder = [
"_____aaaaa_____aaaa_____aaaa____",
"___ccccccc____cccccc____ccccc___",
"__aaaaaaaa___aaaaaaaa___aaaaaa__",
"__cccccccc__bbbbbbbbbb__ccccccc_",
"_aaccccccc_bbbbbbbbbbbb_ccccccca",
"_aaccccccc_bbbbbbbbbbbb_ccccccca",
"________________________________",
"_aaaaaaaaa__cccccccccc__aaaaaaaa",
"_aaaaaaaaa__cccccccccc__aaaaaaaa",
"__aaaaaaaa___aaaaaaaa___aaaaaaa_",
"__aaaaaaaa___aaaaaaaa___aaaaaaa_"
]
先用这个数组渲染横排,然后再用正则匹配的方式匹配这个数组的item:
getItems (item) { // 将字符串转成一个数组的方法,遍历数组中每一个元素
return item.match(/[a-z_]{1}(\[[0-9a-z_]{0,}(,[0-9a-z_ ]+)?\])?/gi);
},
然后渲染座位:
<div id="seatMap">
<div class="seat-charts-row" v-for="(item,x) in seatOrder" :key="x">
<div @click="userChoiceFn(x,y,subItem)" ref="allSeat" class="allPublicGray" v-for="(subItem,y) in getItems(item)" :key="y">
<div v-if="subItem =='a'" :class="{bgBlack: seatGrade[0].off}"><img src="../assets/img/A.png"/></div>
<div v-if="subItem =='b'" :class="{bgBlack: seatGrade[1].off}"><img src="../assets/img/B.png"/></div>
<div v-if="subItem =='c'" :class="{bgBlack: seatGrade[2].off}"><img src="../assets/img/C.png"/></div>
<div v-if="subItem =='_'"></div>
</div>
</div>
</div>
这里渲染完了你会发现,点击座位是出现选中位置的信息,点击座位两侧的空白区,也就是下划线_占位的地方也有选中信息,在点击事件里获取用户选中座位信息以及判断是否点击座位还是_下划线:
userChoiceFn (x,y,subItem) {//x:一行 y:一列 subItem:每个div
var price = 0
if (subItem == 'a') {
price = 120
}else if(subItem == 'b'){
price = 100
}else {
price = 90
}
var obj = { //每次push进右边的选择数组中的数据
'col':x,
'row':y,
'price':price,
'num': x+'-'+y //传给后台字段,根据这个字段找到用户要取消的座位
}
var userSitNum = x*32+y//计算当前用户点击的座位的下标
let oDivs = document.getElementsByClassName('allPublicGray')//选中所有的座位
//console.log(oDivs[userSitNum].off)
oDivs[userSitNum].off = !oDivs[userSitNum].off//用户点击当前座位自定义属性取反,也就是选中和取消
if(oDivs[userSitNum].off){
if(subItem == 'a' || subItem == 'b' || subItem == 'c'){ //判断点击空白处不添加背景和座位信息
this.getSeatCode.push(obj)//点击添加座位信息
this.$refs.allSeat[userSitNum].innerHTML = '<img style="width: 16px;height: 16px" src="../../static/img/geted.png"/>'
this.seatNumber = this.getSeatCode.length//座位个数
//计算总价
var userPrice = this.getSeatCode.map(function (item,index) { //薅出来数组中每项的价格组成新数组
return item.price
})
if(userPrice.length > 1){ //判断当用户只添加一个座位时reduce
this.allMoney = userPrice.reduce(function (prev,next) { //将价格相加
return prev + next
})
}else if(userPrice.length == 1) {
this.allMoney = userPrice[0]
}
//console.log(userPrice)
}
}else { // 这个else下面的判断里面代码重复,大家可以封装一下
if (subItem == 'a'){
var deleteArr = this.getSeatCode.filter(function (item,index) {//筛选出用户取消的座位信息
return item.num == x+'-'+y
})
//console.log(deleteArr)
this.getSeatCode.remove(deleteArr[0])
this.seatNumber = this.getSeatCode.length//计算用户选择的座位总数
//计算总价
var userPrice = this.getSeatCode.map(function (item,index) { //薅出来数组中每项的价格组成新数组
return item.price
})
if(userPrice.length > 1){
this.allMoney = userPrice.reduce(function (prev,next) { //将价格相加
return prev + next
})
}else if(userPrice.length == 1) {
this.allMoney = userPrice[0]
}
this.$refs.allSeat[userSitNum].innerHTML = '<img style="width: 16px;height: 16px" src="../../static/img/A.png"/>'
}else if(subItem == 'b'){
var deleteArr = this.getSeatCode.filter(function (item,index) {
return item.num == x+'-'+y
})
this.getSeatCode.remove(deleteArr[0])
this.seatNumber = this.getSeatCode.length//计算用户选择的座位总数
//计算总价
var userPrice = this.getSeatCode.map(function (item,index) { //薅出来数组中每项的价格组成新数组
return item.price
})
if(userPrice.length > 1){
this.allMoney = userPrice.reduce(function (prev,next) { //将价格相加
return prev + next
})
}else if(userPrice.length == 1) {
this.allMoney = userPrice[0]
}
this.$refs.allSeat[userSitNum].innerHTML = '<img style="width: 16px;height: 16px" src="../../static/img/B.png"/>'
}else if(subItem == 'c'){
var deleteArr = this.getSeatCode.filter(function (item,index) {
return item.num == x+'-'+y
})
this.getSeatCode.remove(deleteArr[0])
this.seatNumber = this.getSeatCode.length//计算用户选择的座位总数
//计算总价
var userPrice = this.getSeatCode.map(function (item,index) { //薅出来数组中每项的价格组成新数组
return item.price
})
if(userPrice.length > 1){
this.allMoney = userPrice.reduce(function (prev,next) { //将价格相加
return prev + next
})
}else if(userPrice.length == 1) {
this.allMoney = userPrice[0]
}
this.$refs.allSeat[userSitNum].innerHTML = '<img style="width: 16px;height: 16px" src="../../static/img/C.png"/>'
}
}
//console.log(this.getSeatCode)
},
代码注释已经写得很明白了,看两遍就懂了,关于左下角三级座位选中凸显,就不细说了,我放一个代码全文,这个文件里面有很多静态布局可以删除,这个页面只要放在能成功运行的vue项目里,就能看到啦,这个项目在我的github,本来可以放地址,但是里面静态页面是在太多了,单是这个页面完全可以独立运行,有细碎的bug自行修补啊:
<template>
<div id="select">
<div class="main">
<div class="brandNav">
不重要的导航
</div>
<div class="seat">
<img src="../assets/img/zuowei_03.jpg"/>
<div class="seat_wrap">
<div class="seat_left">
<!-- 座位分布 -->
<div id="seatMap">
<div class="seat-charts-row" v-for="(item,x) in seatOrder" :key="x">
<div @click="userChoiceFn(x,y,subItem)" ref="allSeat" class="allPublicGray" v-for="(subItem,y) in getItems(item)" :key="y">
<div v-if="subItem =='a'" :class="{bgBlack: seatGrade[0].off}"><img src="../assets/img/A.png"/></div>
<div v-if="subItem =='b'" :class="{bgBlack: seatGrade[1].off}"><img src="../assets/img/B.png"/></div>
<div v-if="subItem =='c'" :class="{bgBlack: seatGrade[2].off}"><img src="../assets/img/C.png"/></div>
<div v-if="subItem =='_'"></div>
</div>
</div>
</div>
<!-- 三等级凸显 -->
<div class="seatStatus">
<ul>
<li v-for="(item, index) in seatGrade" :key="index">
<span>{{item.price}}</span>
<span>{{item.grade}}</span>
<input @click="choiceGradeFn(index)" type="checkbox">
</li>
</ul>
<img src="../assets/img/seatG_03.jpg"/>
</div>
</div>
<div class="seat_right">
<h3>已选座位【{{seatNumber}}】</h3>
<ul v-if="getSeatCode != []">
<li v-for="(item, index) in getSeatCode" :key="index">
<p>座位:{{item.col}}排{{item.row}}号</p>
<p>价格:{{item.price}}</p>
<p>看台:中心剧院</p>
<p>楼层:剧场</p>
</li>
</ul>
<h1 v-if="seatNumber != 0">
<p>总价:{{allMoney}}元</p>
<a @click="toOrder()" href="javascript:;">购买</a>
</h1>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
// 这个数组是座位分布图,a=A级座位,以此类推,下划线_代表空白地方
// 一横排占位32个
var seatOrder = [
"_____aaaaa_____aaaa_____aaaa____",
"___ccccccc____cccccc____ccccc___",
"__aaaaaaaa___aaaaaaaa___aaaaaa__",
"__cccccccc__bbbbbbbbbb__ccccccc_",
"_aaccccccc_bbbbbbbbbbbb_ccccccca",
"_aaccccccc_bbbbbbbbbbbb_ccccccca",
"________________________________",
"_aaaaaaaaa__cccccccccc__aaaaaaaa",
"_aaaaaaaaa__cccccccccc__aaaaaaaa",
"__aaaaaaaa___aaaaaaaa___aaaaaaa_",
"__aaaaaaaa___aaaaaaaa___aaaaaaa_"
]
var seatGrade = [
{
'price':'120元',
'grade':'A级',
'off':false,
},
{
'price':'100元',
'grade':'B级',
'off':false
},
{
'price':'90元',
'grade':'C级',
'off':false
}
]
export default {
name: 'Selection',
data () {
return {
seatOrder, // 座位分布数组
seatGrade, // 三等级划分
getSeatCode:[],// 已选
seatNumber:0, // 座位个数
allMoney:0 // 总价
}
},
components: {
Head,
Footer
},
methods: {
//选座页面的放大缩小和移动
bindScroll() {
// 添加鼠标点下和移动事件,通过这两个事件进行监听,通过修改position的top和left值进行界面的移动。
var oImg = document.getElementById("seatMap");
oImg.onmousedown = function(ev) {
var ev = ev || event;
var disX = ev.clientX - oImg.offsetLeft;
var disY = ev.clientY - oImg.offsetTop;
if (oImg.setCapture) {
oImg.setCapture();
}
document.onmousemove = function(ev) {
var ev = ev || event;
oImg.style.left = ev.clientX - disX + "px";
oImg.style.top = ev.clientY - disY + "px";
};
document.onmouseup = function() {
document.onmousemove = document.onmouseup = null;
//释放全局捕获 releaseCapture();
if (oImg.releaseCapture) {
oImg.releaseCapture();
}
};
return false;
};
// 监听滚轮滚动事件,并使用scale 对页面进行操作,实现界面的放大和缩小。
oImg.onmousewheel = fn;
if (oImg.addEventListener) {
oImg.addEventListener("DOMMouseScroll", fn, false);
}
var scaleSize = 1;
function fn(ev) {
var ev = ev || event;
var b = true;
if (ev.wheelDelta) { //ev.wheelDelta和ev.detail做兼容
b = ev.wheelDelta > 0 ? true : false;
} else {
b = ev.detail < 0 ? true : false;
}
if (b) {
scaleSize += 0.03;
scaleSize = scaleSize > 2 ? 2 : scaleSize;
} else {
scaleSize -= 0.03;
scaleSize = scaleSize < 0.5 ? 0.5 : scaleSize;
}
this.style.transform = "scale(" + scaleSize + ")";
if (ev.preventDefault) {
ev.preventDefault();
}
return false;
}
},
getItems (item) { //封装的将字符串转成一个数组的方法,遍历数组中每一个元素
return item.match(/[a-z_]{1}(\[[0-9a-z_]{0,}(,[0-9a-z_ ]+)?\])?/gi);
},
// 三等级凸显
choiceGradeFn (index) {//三个等级座位分布展示
this.seatGrade[index].off = !this.seatGrade[index].off
//console.log(this.seatGrade[index].off, index)
},
userChoiceFn (x,y,subItem) {//x:一行 y:一列 subItem:每个div
var price = 0
if (subItem == 'a') {
price = 120
}else if(subItem == 'b'){
price = 100
}else {
price = 90
}
var obj = { //每次push进右边的选择数组中的数据
'col':x,
'row':y,
'price':price,
'num': x+'-'+y //传给后台字段,根据这个字段找到用户要取消的座位
}
var userSitNum = x*32+y//计算当前用户点击的座位的下标
let oDivs = document.getElementsByClassName('allPublicGray')//选中所有的座位
//console.log(oDivs[userSitNum].off)
oDivs[userSitNum].off = !oDivs[userSitNum].off//用户点击当前座位自定义属性取反,也就是选中和取消
if(oDivs[userSitNum].off){
if(subItem == 'a' || subItem == 'b' || subItem == 'c'){ //判断点击空白处不添加背景和座位信息
this.getSeatCode.push(obj)//点击添加座位信息
this.$refs.allSeat[userSitNum].innerHTML = '<img style="width: 16px;height: 16px" src="../../static/img/geted.png"/>'
this.seatNumber = this.getSeatCode.length//座位个数
//计算总价
var userPrice = this.getSeatCode.map(function (item,index) { //薅出来数组中每项的价格组成新数组
return item.price
})
if(userPrice.length > 1){ //判断当用户只添加一个座位时reduce
this.allMoney = userPrice.reduce(function (prev,next) { //将价格相加
return prev + next
})
}else if(userPrice.length == 1) {
this.allMoney = userPrice[0]
}
//console.log(userPrice)
}
}else { // 这个else下面的判断里面代码重复,大家可以封装一下
if (subItem == 'a'){
var deleteArr = this.getSeatCode.filter(function (item,index) {//筛选出用户取消的座位信息
return item.num == x+'-'+y
})
//console.log(deleteArr)
this.getSeatCode.remove(deleteArr[0])
this.seatNumber = this.getSeatCode.length//计算用户选择的座位总数
//计算总价
var userPrice = this.getSeatCode.map(function (item,index) { //薅出来数组中每项的价格组成新数组
return item.price
})
if(userPrice.length > 1){
this.allMoney = userPrice.reduce(function (prev,next) { //将价格相加
return prev + next
})
}else if(userPrice.length == 1) {
this.allMoney = userPrice[0]
}
this.$refs.allSeat[userSitNum].innerHTML = '<img style="width: 16px;height: 16px" src="../../static/img/A.png"/>'
}else if(subItem == 'b'){
var deleteArr = this.getSeatCode.filter(function (item,index) {
return item.num == x+'-'+y
})
this.getSeatCode.remove(deleteArr[0])
this.seatNumber = this.getSeatCode.length//计算用户选择的座位总数
//计算总价
var userPrice = this.getSeatCode.map(function (item,index) { //薅出来数组中每项的价格组成新数组
return item.price
})
if(userPrice.length > 1){
this.allMoney = userPrice.reduce(function (prev,next) { //将价格相加
return prev + next
})
}else if(userPrice.length == 1) {
this.allMoney = userPrice[0]
}
this.$refs.allSeat[userSitNum].innerHTML = '<img style="width: 16px;height: 16px" src="../../static/img/B.png"/>'
}else if(subItem == 'c'){
var deleteArr = this.getSeatCode.filter(function (item,index) {
return item.num == x+'-'+y
})
this.getSeatCode.remove(deleteArr[0])
this.seatNumber = this.getSeatCode.length//计算用户选择的座位总数
//计算总价
var userPrice = this.getSeatCode.map(function (item,index) { //薅出来数组中每项的价格组成新数组
return item.price
})
if(userPrice.length > 1){
this.allMoney = userPrice.reduce(function (prev,next) { //将价格相加
return prev + next
})
}else if(userPrice.length == 1) {
this.allMoney = userPrice[0]
}
this.$refs.allSeat[userSitNum].innerHTML = '<img style="width: 16px;height: 16px" src="../../static/img/C.png"/>'
}
}
//console.log(this.getSeatCode)
},
toOrder () {//点击购买
this.$router.push('/order')
this.$store.commit('getSitcode',this.getSeatCode)
//console.log(this.$store.state.sitCode)
},
},
mounted () {
this.bindScroll()
// 初始所有座位处于未选中状态
// 应该是后台会返回一个已选过的座位数组给前端,前端将已被选中的座位进行标识,并且设置为只读,这里的算法是x*32+y = 已被选中的座位下标
let oDivs = document.getElementsByClassName('allPublicGray')
for (var i = 0; i < oDivs.length ; i++){
oDivs[i].off = false
}
Array.prototype.indexOf = function(val) {
for (var i = 0; i < this.length; i++) {
if (this[i] == val) return i;
}
return -1;
}
Array.prototype.remove = function(val) { //封装的数组删除指定元素的方法,在217行调用
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
}
}
}
</script>
<style scoped lang="less">
#select{
background: #f5f5f5 ;
.main{
width: 1200px;
margin: 0 auto;
overflow: hidden;
.brandNav{
margin-bottom: 40px;
.el-breadcrumb{
margin-left: 15px;
}
}
.seat{
background: #fff;
margin-bottom: 20px;
&>img{
width: 1202px;
}
.seat_wrap{
display: flex;
justify-content: space-between;
.seat_left{
width: 1000px;
height: 674px;
background: #fff;
position: relative;
overflow: hidden;
#seatMap{
width: 800px;
height: 574px;
position: absolute;
left: 100px;
top: 50px;
.seat-charts-row{
margin-bottom: 32px;
}
.allPublicGray{
width: 18px;
height: 18px;
display: inline-block;
margin-right: 7px;
text-align: center;
line-height: 18px;
img{
width:16px;
height: 16px;
display: inline-block;
}
}
.bgBlack{
background: url("../assets/img/black.png") no-repeat;
background-size: 18px;
}
}
.seatStatus {
position: absolute;
border: 1px solid red;
width: 200px;
height: 200px;
left: 0;
bottom: 0;
background: #fff;
border-radius: 8px;
&>img{
margin-left: 20px;
}
ul{
font-size: 12px;
margin: 20px;
border-bottom: 1px solid #ccc;
li{
margin-bottom: 10px;
padding: 0 5px;
span:first-of-type{
background: red;
display: inline-block;
width: 56px;
text-align: center;
}
span:last-of-type{
font-weight: bold;
padding-left: 10px;
}
input{
margin-left: 10px;
}
}
li:nth-of-type(2){
span:first-of-type{
background: #fd68a6;
}
}
li:nth-of-type(3){
span:first-of-type{
background: #65a9fd;
}
}
}
}
}
.seat_right{
width: 200px;
height: 674px;
background: #efefef;
overflow:auto ;
h3{
background: #eee;
height: 30px;
line-height: 30px;
font-size: 17px;
text-align: center;
font-weight: normal;
margin-bottom: 10px;
}
h1{
text-align: center;
p{
font-size: 16px;
color: red;
height: 40px;
line-height: 40px;
}
a{
display: block;
width: 100px;
height: 32px;
text-align: center;
line-height: 32px;
margin: 0 auto;
font-size: 14px;
color: #fff;
background: red;
border-radius: 5px;
}
}
ul{
font-size: 14px;
li{
border-bottom: 1px solid #ccc;
margin-bottom: 10px;
padding-left: 10px;
border-radius: 10px;
}
}
}
}
}
}
}
</style>
完了
https://blog.csdn.net/huanhuan03/article/details/103868498
更多推荐
所有评论(0)