影院选座功能是电影购票平台的核心交互模块之一,其核心目标是通过直观的界面与流畅的交互,帮助用户高效完成座位选择。本文将通过 HTML + CSS + JavaScript 实现一个影院选座页面,包含网格化座位布局、不同状态座位管理和实时数据统计三大核心功能。
效果演示
界面布局
- 顶部银幕提示
- 中间座位区域:网格布局,不同状态的座位颜色不同
- 状态说明:可用(灰色)、已选(绿色)、已售(红色,不可选)
- 选座信息
- 操作按钮
<div class="container">
<!-- 银幕展示 -->
<div class="screen">银幕</div>
<!-- 座位区域 -->
<div class="seat-map" id="seat-map"></div>
<!-- 状态说明 -->
<div class="status">...</div>
<!-- 选座信息 -->
<div class="selected-info">
<div id="selected-seats">暂未选座</div>
</div>
<!-- 操作按钮 -->
<div>
<div class="btn" id="btn">请先选座</div>
</div>
</div>
座位区域的样式
.seat-map {
display: grid;
gap: 10px;
justify-content: center;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.seat-row {
display: flex;
gap: 10px;
align-items: center;
}
.seat {
width: 30px;
height: 30px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
定义基础数据
// 单价
const pricePerSeat = 45;
// 座位数据
const seatData = [
[1, 1, 1, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
];
交互功能
初始化座位图
function initSeatMap() {
seatData.forEach((row, rowIndex) => {
const rowDiv = document.createElement('div');
rowDiv.className = 'seat-row';
for (let i = 0; i < row.length; i++) {
const seat = document.createElement('div');
seat.className = row[i] === 0 ? 'seat sold' : 'seat available';
seat.textContent = i+1;
seat.dataset.row = rowIndex + 1;
seat.dataset.number = i+1;
seat.dataset.status = row[i];
rowDiv.appendChild(seat);
}
seatMapElem.appendChild(rowDiv);
});
}
点击切换座位状态
seatMapElem.addEventListener('click', (e) => {
const seat = e.target.closest('.seat');
if (!seat || seat.classList.contains('sold')) return;
const currentStatus = seat.dataset.status;
if (currentStatus === '1') { // 可选
seat.classList.remove('available');
seat.classList.add('selected');
seat.dataset.status = '2';
} else { // 已选
seat.classList.remove('selected');
seat.classList.add('available');
seat.dataset.status = '1';
}
updateSelectedInfo();
});
实时显示已选座位,并计算总价
function updateSelectedInfo() {
const selected = document.querySelectorAll('#seat-map .selected');
const seatsList = Array.from(selected).map(seat => ({
row: seat.dataset.row,
number: seat.dataset.number
}));
const selectedSeatsElem = document.getElementById('selected-seats');
selectedSeatsElem.innerHTML = '';
if (seatsList.length === 0) {
selectedSeatsElem.textContent = '暂未选座';
} else {
seatsList.forEach(seat => {
const seatDiv = document.createElement('div');
seatDiv.className = 'selected-seat-item';
seatDiv.innerHTML = `<span>${seat.row}排${seat.number}座</span>
<span>¥${pricePerSeat}</span>`;
selectedSeatsElem.appendChild(seatDiv);
});
}
const totalPrice = selected.length * pricePerSeat;
const btnElem = document.getElementById('btn');
btnElem.textContent = selected.length > 0 ? `确认选座 (¥${totalPrice})` : '请先选座';
}
完整代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>影院选座</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #f5f5f5;
font-family: -apple-system, sans-serif;
}
.container {
max-width: 750px;
margin: 0 auto;
padding: 20px;
}
.screen {
text-align: center;
margin: 20px 0;
color: #666;
}
.seat-map {
display: grid;
gap: 10px;
justify-content: center;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.seat-row {
display: flex;
gap: 10px;
align-items: center;
}
.seat {
width: 30px;
height: 30px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.status-color.available, .seat.available {
background: #e0e0e0;
color: #666;
}
.status-color.selected, .seat.selected {
background: #03fb81;
color: white;
}
.status-color.sold, .seat.sold {
background: #ff4d4f;
color: white;
cursor: not-allowed;
}
.status {
display: flex;
gap: 20px;
justify-content: center;
margin: 20px 0;
}
.status-item {
display: flex;
align-items: center;
gap: 5px;
}
.status-color {
width: 20px;
height: 20px;
border-radius: 3px;
}
.selected-info {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
max-width: 750px;
margin: 0 auto;
}
#selected-seats {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 10px;
}
.selected-seat-item {
background-color: #f0f0f0;
border-radius: 6px;
padding: 8px;
text-align: center;
font-size: 12px;
color: #333;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.btn {
max-width: 750px;
margin: 20px auto;
padding: 12px;
text-align: center;
background-color: #30c501;
color: white;
font-weight: bold;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.btn:hover {
background-color: #02d66d;
}
.btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<div class="screen">银幕</div>
<div class="seat-map" id="seat-map"></div>
<div class="status">
<div class="status-item">
<div class="status-color available"></div>
<span>可选</span>
</div>
<div class="status-item">
<div class="status-color selected"></div>
<span>已选</span>
</div>
<div class="status-item">
<div class="status-color sold"></div>
<span>已售</span>
</div>
</div>
<div class="selected-info">
<div id="selected-seats">暂未选座</div>
</div>
<div>
<div class="btn" id="btn">请先选座</div>
</div>
</div>
<script>
// 单价
const pricePerSeat = 45;
// 生成座位数据
const seatData = [
[1, 1, 1, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
];
const seatMapElem = document.getElementById('seat-map');
// 座位点击事件
seatMapElem.addEventListener('click', (e) => {
const seat = e.target.closest('.seat');
if (!seat || seat.classList.contains('sold')) return;
const currentStatus = seat.dataset.status;
if (currentStatus === '1') { // 可选
seat.classList.remove('available');
seat.classList.add('selected');
seat.dataset.status = '2';
} else { // 已选
seat.classList.remove('selected');
seat.classList.add('available');
seat.dataset.status = '1';
}
updateSelectedInfo();
});
// 初始化座位图
function initSeatMap() {
seatData.forEach((row, rowIndex) => {
const rowDiv = document.createElement('div');
rowDiv.className = 'seat-row';
// 添加座位
for (let i = 0; i < row.length; i++) {
const seat = document.createElement('div');
seat.className = row[i] === 0 ? 'seat sold' : 'seat available';
seat.textContent = i+1;
seat.dataset.row = rowIndex + 1;
seat.dataset.number = i+1;
seat.dataset.status = row[i];
rowDiv.appendChild(seat);
}
seatMapElem.appendChild(rowDiv);
});
}
// 更新选座信息
function updateSelectedInfo() {
const selected = document.querySelectorAll('#seat-map .selected');
const seatsList = Array.from(selected).map(seat => ({
row: seat.dataset.row,
number: seat.dataset.number
}));
// 动态更新座位信息容器
const selectedSeatsElem = document.getElementById('selected-seats');
selectedSeatsElem.innerHTML = ''; // 清空当前内容
if (seatsList.length === 0) {
selectedSeatsElem.textContent = '暂未选座';
} else {
seatsList.forEach(seat => {
const seatDiv = document.createElement('div');
seatDiv.className = 'selected-seat-item';
seatDiv.innerHTML = `<span>${seat.row}排${seat.number}座</span>
<span>¥${pricePerSeat}</span>
`;
selectedSeatsElem.appendChild(seatDiv);
});
}
// 更新总价和按钮文字
const totalPrice = selected.length * pricePerSeat;
const btnElem = document.getElementById('btn');
btnElem.textContent = selected.length > 0 ? `确认选座 (¥${totalPrice})` : '请先选座';
}
// 初始化
initSeatMap();
</script>
</body>
</html>