影院选座功能实现:从UI设计到交互逻辑

影院选座功能是电影购票平台的核心交互模块之一,其核心目标是通过直观的界面与流畅的交互,帮助用户高效完成座位选择。本文将通过 HTML + CSS + JavaScript 实现一个影院选座页面,包含网格化座位布局、不同状态座位管理和实时数据统计三大核心功能。

效果演示

image-20250524203354068
image-20250524203502332

界面布局

  • 顶部银幕提示
  • 中间座位区域:网格布局,不同状态的座位颜色不同
  • 状态说明:可用(灰色)、已选(绿色)、已售(红色,不可选)
  • 选座信息
  • 操作按钮
<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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值