需求描述

如图, 有4个区域需要注意
- 影片选择区, 要能获取到选择的影片对应的价格.
- 标记示例区, 与下方的图标颜色形状大小一致, 但只是示例, 不参与用户交互, 不与选座数据混淆.
- 荧幕区, 用css模拟出第一视角的观看体验.
- 座位区, 白色为不可选, 灰色为可选, 选中后变为蓝色, 再次点击变为灰色.
- 数据统计区, 能统计已选座位数, 并根据单价计算总费用.
实现
HTML部分
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><link rel="stylesheet" href="style.css"><title>电影座位预订</title></head><body><h1>欢迎来到米修影院</h1><div class="movie-container"><label for="movie">选择影片</label><select id="movie"><option value="32">寄生虫 (票价: 32元)</option><option value="35">小丑 (票价: 35元)</option><option value="38">好莱坞往事 (票价: 38元)</option><option value="30">玩具总动员 (票价: 30元)</option></select></div><ul class="showcase"><li><div class="seat"></div><small>可选</small></li><li><div class="seat selected"></div><small>已选</small></li><li><div class="seat occupied"></div><small>不可选</small></li></ul><div class="container"><div class="screen"></div><div class="row"><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div></div><div class="row"><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat occupied"></div><div class="seat occupied"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div></div><div class="row"><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat occupied"></div><div class="seat occupied"></div></div><div class="row"><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div></div><div class="row"><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat occupied"></div><div class="seat occupied"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div></div><div class="row"><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat"></div><div class="seat occupied"></div><div class="seat occupied"></div><div class="seat occupied"></div><div class="seat"></div></div></div><p class="text">您已经选择了 <span id="count">0</span> 个座位, 总计票价为 <span id="total">0</span> 元</p><script src="script.js"></script></body></html>
这个HTML感觉写得有点蠢. 先实现功能吧, 后面有时间再来优化.
CSS部分
* {margin: 0;padding: 0;box-sizing: border-box;}body {background: #242333;display: flex;flex-direction: column;align-items: center;justify-content: center;min-height: 100vh;font-family: "Helvetica", "Arial", sans-serif;}.movie-container {margin: 20px 0;}.movie-container select {background-color: #fff;border: 0;border-radius: 5px;font-size: 14px;margin-left: 10px;padding: 5px 15px;/*以下是对于苹果系统的设置, 不设置的话padding会没有效果*/-moz-appearance: none;-webkit-appearance: none;appearance: none;}.seat {height: 12px;width: 15px;background-color: #444451;margin: 3px;border-top-right-radius: 10px;border-top-left-radius: 10px;}.seat.selected {background-color: #6feaf6;}.seat.occupied {background-color: #fff;}/*选中从前往后第2个*/.seat:nth-of-type(2) {margin-right: 18px;}/*选中从后往前第二个*/.seat:nth-last-of-type(2) {margin-left: 18px;}/*可选和已选都有鼠标移入动画, 这里用:not()把 .occupied去除掉*/.container .seat:not(.occupied):hover {cursor: pointer;transform: scale(1.2);}/*.seat:not(.occupied) {*//* transition: all 0.2s ease;*//*}*/.showcase {background-color: rgba(0, 0, 0, 0.1);padding: 5px 10px;border-radius: 5px;color: #777;list-style-type: none;display: flex;justify-content: space-between;margin-bottom: 10px;}.showcase li {display: flex;align-items: center;justify-content: center;margin: 0 10px;}.showcase li small {margin-left: 2px;}.container {/*添加视距*/perspective: 1000px;margin-bottom: 10px;}.screen {background-color: #fff;height: 70px;width: 100%;margin: 15px 0;transform: rotateX(-45deg); /*在父元素中添加视距才能看到3D效果*/box-shadow: 0 3px 10px rgba(255, 255, 255, 0.7);}.row {display: flex;}.text {margin: 5px 0;}.text span {color: #6feaf6;}
这里的重点: 对苹果系统的兼容( apperance 属性), 伪类选择器 :nth-of-type(n) 和 :not() 的应用, transform-totate 和 perspective的结合应用.
JavaScript部分
const container = document.querySelector('.container')const seats = document.querySelectorAll('.row .seat:not(.occupied)')const count = document.getElementById('count')const total = document.getElementById('total')const movieSelect = document.getElementById('movie')//value是字符串, 用+号饮食转换成Numberlet ticketPrice = +movieSelect.value//初始化渲染座位和影片populateUi()//更新座位数及总票价function updateSelectedCount(){//获取已选座位的数组const selectedSeats = document.querySelectorAll('.row .seat.selected')//map()方法会像forEach()那样遍历一个数组, 然后对每项处理后返回一个新的数组const seatsIndex = [...selectedSeats].map(seat => [...seats].indexOf(seat))localStorage.setItem('selectedSeats', JSON.stringify(seatsIndex))const selectedSeatsCount = selectedSeats.lengthcount.innerText = selectedSeatsCount.toString()total.innerText = (selectedSeatsCount * ticketPrice).toString()}//保存电影索引值和票价function setMovieData(movieIndex, moviePrice){localStorage.setItem('selectedMovieIndex', movieIndex)localStorage.setItem('selectedMoviePrice', moviePrice)}//封装函数获取本地数据并渲染样式function populateUi(){//获取座位信息const selectedSeats = JSON.parse(localStorage.getItem('selectedSeats'))if (selectedSeats !== null && selectedSeats.length > 0){seats.forEach((seat, index) => {if (selectedSeats.indexOf(index) > -1){seat.classList.add('selected')}})}//获取影片信息const selectedMovieIndex = localStorage.getItem('selectedMovieIndex')console.log(selectedMovieIndex)if (selectedMovieIndex !== null){movieSelect.selectedIndex = selectedMovieIndex}}//监听电影下拉框movieSelect. addEventListener('change', e => {ticketPrice = +e.target.valuesetMovieData(e.target.selectedIndex, e.target.value)updateSelectedCount()})//绑定座位点击事件, 时间冒泡container.addEventListener('click', e => {// console.log(e.target)//使用classList数组的contains方法, 检测数组中是否包含某一项if (e.target.classList.contains('seat') &&!e.target.classList.contains('occupied')){//使用classList 的toggle方法, 有则删除, 无则加入e.target.classList.toggle('selected')updateSelectedCount()}})//设置初始座位和总票价updateSelectedCount()
这里有一个重要bug, 页面加载完数据后, 再切换影片, 再刷新页面, 显示的还是之前的票价, 待修复.
