Skip to content

清华大学软件学院2025夏季学期《Web前端技术实训》课程,三四五组大作业:电影院选座

Notifications You must be signed in to change notification settings

tingyunaiai9/345-movie-seat-booking

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

三四五:电影院选座

清华大学软件学院2025夏《Web前端技术实训》课程

三四五组大作业:电影院选座

[TOC]

一、项目介绍

1.1 预览

展示pv

1.2 项目背景

  • 项目目标:使用 Canvas 技术设计并实现一个可交互的电影院选座软件
  • 核心需求:支持个人与团体票的自动和手动选座,并根据不同年龄段(少年、成年、老年)的观众遵循特定的选座规则

1.3 实现功能预览

  • 基础功能

    • 影厅绘制:基于 Canvas 绘制弧形排列的电影院座位图,包含明确的排号和座位号
    • 座位状态:不同颜色直观展示座位状态
    • 选座模式:支持单选、多选,以及根据规则的智能自动选座
    • 票务管理:实现了预订、付款购票、取消预定、退票退款等一系列票务操作流程
  • 扩展功能

    • 不同大小的放映厅:通过静态配置参数实现96人、200人、300人的放映厅
    • 动态配置:支持通过页面上的交互组件自定义不同大小的影厅
  • 优点

    • 视觉美感:配色清新;动态效果;为每场电影定制皮肤;特别设计猫猫座位
    • 人性化设计:页面明了,指引清晰,易操作;互动体验感强;响应式布局
    • 内涵彩蛋,欢迎探索~
    • 功能性优点
      • 智能自动选座:黄金选座区一键锁定,让每次购票省时又省心
      • 全周期订单管理中心:所有订单一目了然
      • 实时同步的操作反馈:用户操作均有即时的反馈,让每一个决策都心中有数

1.4 项目结构

本项目采用标准的前端开发结构,通过将HTML、CSS和JavaScript分离,实现了逻辑与表现的解耦。项目遵循模块化开发的思想,将不同功能的代码拆分到独立的模块中,结构清晰,便于团队协作、维护和扩展。

.
│  readme.md
├─pic/
└─src/
   │  index.html
   │  hello.html
   ├─css/
   ├─img/
   └─js/

根目录文件

  • readme.md: 项目说明文档,介绍项目概况、技术栈及使用方法
  • pic/: 存放项目文档中使用的图片
  • src/: 核心源代码目录,包含了应用全部的前端资源

src/ 源代码目录

  • index.html & hello.html: 项目的 HTML 入口文件,承载应用的基本结构
  • css/ (样式目录): 存放所有样式表文件
    • base.css: 定义全局通用样式、CSS 变量及浏览器样式重置
    • layout.css, components.css: 定义页面主布局与可复用的公共组件样式
    • responsive.css: 响应式样式,用于适配不同尺寸的设备
    • 其他 *.css 文件: 各功能视图(如选座、订单、支付等)的独立模块化样式
  • js/ (脚本目录): 存放所有 JavaScript 逻辑代码,遵循数据与 UI 分离原则
    • main.js, stateManager.js: 核心数据处理、业务算法及全局状态管理模块
    • canvas.js: 专门负责 Canvas 座位图的动态绘制与更新
    • ui-*.js (UI 模块组): 处理各视图的用户界面交互和 DOM 操作
    • ui-validation.js: 负责前端业务逻辑的验证,如表单输入、选座规则校验
  • img/ (资源目录): 存放项目 UI 界面直接使用的图片资源

二、使用说明

2.1 运行环境与方法

  • 推荐浏览器:Google Chrome 浏览器的最新稳定版
  • 打开方式:直接在浏览器中打开 index.html 即可打开选座系统;也可以打开 hello.html 进入欢迎页面,再从欢迎页面跳转到选座系统

2.2 界面布局介绍及操作指南

本次作业网站的主要购票流程,由五个界面组成,分别是影厅配置界面、电影选择界面、选座界面、支付界面、确认界面。此外,另设订单界面用于订单管理。

2.2.1 影厅配置界面

  • 进入网页自动打开影厅配置界面,该页面可以选择影厅规模(小厅、中厅、大厅以及自定义)。其中自定义如下图所示,可以通过输入排数以及每排座位数设置影厅大小。

2.2.2 电影选择界面

  • 选择完影厅规模后点击下方按钮 下一步:选择电影 跳转至电影选择界面,该界面布局十分简洁,共有三步电影可供选择,分别是罗小黑战记、蓦然回首以及情书,在电影名下方有对应的放映时间以及电影票价,信息完备。

    此外,我们实现了根据不同电影更换页面背景的功能,分别为三部电影设置了对应的背景,进一步美化了页面。

2.2.3 选座界面

  • 选择完电影后点击下方按钮 下一步:选择座位 跳转至选座界面,选座界面分为上中下三部分。 页面上部的左侧主要负责客户信息输入,同时提供个人票与团体票两种购买方式,两种购买方式均可购买单张或多张票,仅在购票逻辑上有所不同。 同时在右侧我们提供了选座说明,用于提醒客户选座规则,在下方我们设置了自动选座按钮,自动为客户提供可选座位中的最优座位。

    在页面的中部我们用canvas技术实现了影厅选座视图的绘制,用不同颜色表示座位的不同状态,并在下方予以说明。除此之外,我们添加了根据不同电影主题更换座位图片的功能,如下图所示,选择《罗小黑战记》时座位自动变为猫猫头的样子ouo 在此,我们用中厅以及大厅的页面布局作为演示。 用户可以通过单击单选座位,也可以通过**Ctrl+单击多选座位**,相同座位重复点击则视为取消选座

    此外,我们提供了切换布局的按钮,用于在扇形视图与平行视图之间进行切换。

    在页面的下部,我们提供了已选座位的信息,用于帮助用户核对已选座位的状态,此时如若用户选择了座位,则会有座位信息相关的显示,点击信息右侧的 'x' 即可取消该座位的选座。同时我们提供了影厅状态,帮助客户清晰了解目前影厅的上座率等相关信息。 中间区域有预订座位以及直接购票的按钮,点击预订座位会跳过支付与确认页面,直接跳转至结算页面,而点击直接购票则会跳转至支付页面进行进一步确认。

2.2.4 支付界面

  • 点击直接购票后跳转至支付界面,该页面主要供客户核对个人信息及选座信息是否无误,同时进行支付方式的选择,此处我们设置了四种支付方式,可以通过单击进行选择(双击有彩蛋ouo),同时可以尝试把鼠标悬停在问号图标上,有惊喜文字(x)

2.2.5 确认界面

  • 在支付页面点击确认支付后跳转至最终确认页面,该页面负责对用户信息、座位信息以及支付相关信息做最后核对,确保支付流程的完整性与安全性。

  • 若客户确保以上信息都准确无误则可以点击确认支付按钮,跳转至结算彩蛋页面

2.2.6 订单界面

  • 除此之外,我们设计了订单界面,用于查看用户截至目前为止所有类型的订单,包括已支付、预约中、已过期、已取消等,在该界面点击查看订单详情可以查看详细信息。

    详情界面如下,点开已支付的订单可以看到全部相关信息,在最下方有申请退款按钮,用户可以通过订单界面管理全部订单,操作简便。

2.2.7 其他界面

  • 开始界面:进入系统的开始界面,点击“开始预订”进入购票流程

  • 结束界面:预订/购票完成后的结束界面,点击“查看我的订单”进入订单页面,点击“返回首页”进入影厅配置界面

三、实现思路及技术应用

3.1 系统流程设计

  • 核心购票流程设计如下图所示

  • petri网版本流程图

3.2 数据结构设计

系统中的关键数据结构为:seatticket

3.2.1 seat

  • seat 表示一个座位的信息,存储于二维数组 cinemaSeats
  • 变量及取值范围、含义
    • row

      • 类型:Number
      • 取值与含义: 表示座位所在的排号,是一个从 1 开始的整数
    • col

      • 类型:Number
      • 取值与含义: 表示座位所在的列号/座位号,是一个从 1 开始的整数
    • id

      • 类型:String
      • 取值与含义: 每个座位的唯一标识符,格式为 "seat-行号-列号"
    • status

      • 类型:String

      • 取值与含义: 表示座位的核心占用状态。它的值来源于 SEAT_STATUS 常量,主要有以下几种:

        • 'available': 可用。表示该座位是空的,可以被任何人预订或购买
        • 'reserved': 已预订。表示该座位已被用户锁定,但尚未付款,等待支付
        • 'sold': 已售出。表示该座位已被用户成功付款购买
        • 'selected'已选中。表示该座位在选作阶段已被用户选中
      • 状态转移如下图所示

3.2.2 ticket

  • ticket 表示一个订单的信息,存储于一维数组 ticketRecords

  • 变量及取值范围、含义

    • ticketId

      • 类型:String
      • 取值与含义: 票务的唯一订单号,预订票的ID以 "r-" 开头,直接购买的票以 "s-" 开头,后面跟着一个时间戳
    • status

      • 类型:String

      • 取值与含义: 表示这笔交易的当前状态,有以下状态

        • 'reserved': 已预订。交易已创建,但未支付
        • 'sold': 已售出。交易已成功支付
        • 'cancelled': 已取消。一个 'reserved' 状态的订单被用户主动取消
        • 'expired': 已过期。一个 'reserved' 状态的订单因超时未支付而被系统自动作废
        • 'refunded': 已退款。一个 'sold' 状态的订单被成功退款
      • 状态转移如下图所示

    • seats

      • 类型:一维数组,数组中的变量类型为String,即座位的 id 号
      • 取值与含义: 一个数组,包含了本次交易中所有座位的 id
        • 例如 ['seat-8-12', 'seat-8-13'],表示这张票对应了8排12座和8排13座
    • customerInfo

      • 类型:Object
      • 取值与含义: 存储购票人的信息
    • movieInfo

      • 类型: Object
      • 取值与含义: 存储当前订单关联的电影信息
    • createdAt

      • 类型:Date
      • 取值与含义: 一个 Date 对象,记录了这笔交易创建的时间
    • expiresAt

      • 类型:Date or undefined
      • 取值与含义: 仅在 status'reserved' 时存在。一个 Date 对象,表示预订的失效时间点。如果用户不在此时间前完成支付,预订将被作废。支付成功后该属性会被删除。
    • paidAt

      • 类型:Date or undefined
      • 取值与含义: 仅在 status'sold' 时存在。一个 Date 对象,记录了订单成功支付的时间
    • unitPrice

      • 类型: Number
      • 取值与含义: 订单中单张票的价格
    • totalCost

      • 类型: Number
      • 取值与含义: 订单的总金额

3.3 核心算法设计

3.3.1 自动选座算法

选座系统中,设置了人性化的自动选座算法。自动选座算法分为个人选座和团体选座两种模式,用于不同的场景。该部分代码主要位于 main.js 模块中。

  • 个人自动选座算法
    • 此算法适用于用户选择“个人票”时,支持单选、多选
    • 策略:先中间,后两边;先连续,后分散
      • 中心区域优先:优先尝试在影厅的“黄金观影区”(即中心区域)寻找座位
      • 连续座位优先:优先将一个订单内的所有成员安排在连续座位上,以方便交流
      • 优先级:中心区域的连续座位 > 中心区域的分散座位 > 所有区域的连续座位 > 所有区域的分散座位
  • 团体自动选座算法
    • 此算法适用于用户选择“团体票”时,支持单选、多选
    • 策略:保持邻近
      • 优先选择靠前的同一排的相邻座位
      • 若团体人数大于单排的容量,则采用“跨排选座”策略,选择:数个被完全占满的、连续的排,加上最后排的部分连续座位

3.3.2 选座验证算法

在用户确认座位并进行下一步操作(预定座位/直接购票)之前,系统会执行选座验证算法,检查用户所选座位与所填观众数据等是否遵循选座规则。该部分代码主要位于 ui-validation.js 模块中。

  • 通用验证规则

    无论用户选择个人票还是团体票,都必须通过以下基础验证

    1. 必须选择座位:用户不能在未选择任何座位的情况下继续操作
    2. 人数与座位数匹配:选中的座位总数必须与添加的成员总数完全一致
    3. 年龄限制验证:系统会为每一位成员和其选择的座位进行匹配验证。根据成员的年龄判断其所属的年龄组(儿童、成人、老人),并检查该年龄组是否被允许坐在指定的排上
  • 特定票种规则

    在通过通用验证后,系统会根据票种执行特定的规则

    • 个人票:没有额外的限制
    • 团体票:除通用规则外,还必须满足:
      • 座位必须连续
      • 单排连续:若团体人数 $\leq$ 单排容量,所有座位都在同一排,且这些座位的列号必须是连续的
      • 跨排连续:若团体人数 $>$ 单排容量,座位需要在各自的排内连续,所在的排号本身也必须是连续的。除了最后一排,前面的所有排都必须被完全坐满

3.3.3 订单操作算法

main.js 模块负责了票务系统的核心算法,包括预订、直接购票、为预订订单付款、取消预订、退票等一系列票务操作。

模块中维护了订单 ticket 的数组 ticketRecords ,与 localStorage 进行同步,实现了订单信息的保存。

订单操作的核心为创建新订单,与修改订单状态。

  • 创建新订单
    • 以“前缀+时间戳”的形式,为每个订单生成订单 id,其中 s- 为直接购票订单,r- 为预订订单
    • 将订单的状态设置为 soldreserved ,并记录创建订单时的相关时间信息
    • 在订单中记录座位、用户、电影、票价等信息
    • 修改订单中的座位状态
    • 同步至 localStorage
  • 修改订单状态
    • 根据订单 id,搜索到对应订单,并修改其订单状态
    • 根据相关操作,修改订单记录的信息
    • 根据相关操作,更改订单涉及的座位的状态
    • 同步至 localStorage

3.4 数据存储机制

影院涉及关键数据分为以下几个部分,根据调用需求不同以不同方式存储:

  • 当前选择电影信息:每一个电影和一个影厅大小可以确定一个特定影厅

    • 存储方式:以对应的Key存在localStorage中。
    • KeyselectedMovieInfo
    • 存储内容:行列信息colsrows,电影信息idtitle,电影贴图路径image,票价price,开始时间time
  • 影院座位状态数据:根据每一个电影和一个影厅大小可以确定的特定影厅,储存其座位状态。

    • 存储方式:以对应的Key存在localStorage中。
    • KeycinemaState-{电影名}-{rows}x{cols}
    • 存储内容:二维数组cinemaSeats,每个元素包含对应作为的row colidstatus信息。
  • 选座界面输入数据:选座界面接受的输入有客户输入的信息以及客户选择的具体座位。

    • 存储方式:客户信息直接存储在 DOM 元素中,而选中座位则直接更改main.js中的座位状态并实时查询。
    • 存储内容:票务类型,用户姓名及年龄。
  • 订单信息:订单信息包含座位信息、客户信息以及电影信息。

    • 存储方式:以对应的Key存在localStorage中。
    • KeymovieTicketOrders
    • 存储内容:为一个订单列表,每个订单包含信息有:购票IdticketId,订单状态status,座位seats,客户信息customerInfo,电影信息movieInfo,单价unitPrice,总价totalCost,创建时间createdAt,过期时间expiresAt(预订票),支付时间paidAt(直购票或支付后的预定票)。

3.4.1 当前选择电影信息

需要由前两个页面决定,因此先在第一个页面暂存行列信息后,进入第二个页面后得到所选电影,由此得到电影id、电影名、开始时间等信息,在初始化影院选座界面的实际存入localStorage,后续过程中对该信息只读不写,直到整个订单流程结束后,在返回配置界面时清除电影信息在localStorage中的存储,等待新订单重复前面的流程。

3.4.2 影院座位状态数据

整个订单流程中,影院座位状态数据经历了三个阶段。

  • 初始化阶段:由前面两个界面选择结束后,可确定影院座位状态数据的Key,根据Key在localStorage中查找是否有了对应的座位状态信息,如果已经有就加载进当前的cinemaSeats,若没有,则初始化对应大小的影厅并存入localStorage等待后续调用。
  • 选座阶段:这一阶段仅会涉及到availableselected之间的切换,在订单确认之前无需存入localStorage,只需完成交互即可。
  • 订单阶段:订单完成后需要根据订单具体情况更改cinemaSeats的相应座位状态并存入localStorage,预定完成和支付完成两个动作只需要更改当前的信息即可,但是涉及到检查预定是否过期、退款、取消预定、支付预定、退款这几个操作则可能去更改当前cinemaSeats所存储座位状态以外的数据。因此需要根据订单存储的电影信息来查找对应Key并该Key下存储的影院座位状态数据。

3.4.3 选座界面输入数据

客户信息只需要调用getGroupMembersList()getIndividualMembersList()查询DOM来获取即可,而座位信息在处理订单相关的函数中只需要遍历cinemaSeats查看座位是否为selected状态即可。

3.4.4 订单信息

在每一个与订单相关的操作完成后都同步到localStorage中,在系统启动时自动从localStorage中加载订单。

3.5 Canvas绘制

canvas.js 文件负责将影院座位数据以可视化方式呈现在页面上,核心机制如下:

3.5.1 数据来源

  • 座位数据:从main.js获取,得到二维数组,每个元素为座位对象(包含 row、col、id、status 等)。
  • 当前影厅配置:从main.js获取,包括总排数、总列数等。
  • 选中状态:从stateManager.js获取获取当前被选中的座位列表,辅助高亮显示。

3.5.2 绘制流程

canvas.js维护一个 GLOBAL_STATE 对象,记录当前画布、布局类型、行列数、中心区域信息等,确保绘制时状态一致。

  • 初始化与绘制:首先完成 Canvas 初始化、座位数据加载、图片预加载,并调用 drawCinema() 完成实际绘制。

  • 核心绘制函数

    drawCinema()负责整体绘制流程,包括:

    • 清空画布
    • 绘制中央过道虚线
    • 计算并绘制中心区域
    • 遍历所有座位,调用 drawSeat(x, y, seat)绘制每个座位
    • 绘制中心区域标识(矩形或扇形,取决于布局)
  • 座位绘制 drawSeat(x, y, seat)根据座位状态(available、selected、sold、reserved)选择不同图片或颜色,并在座位中心绘制排号和座号。若座位被选中或悬停,会有缩放高亮效果。

  • 布局支持 支持弧形(ARC)和矩形(PARALLEL)两种布局,通过 GLOBAL_STATE.currentLayout 切换,分别采用不同的坐标计算方式。

3.5.3 交互与刷新

  • 布局切换 通过 toggleLayout() 切换布局类型,并自动重绘。

  • 数据变更刷新 调用 refreshCinemaDisplay() 可在座位数据变化后重新绘制,确保画面与数据同步。

  • 页面加载时自动初始化并绘制影院布局。

  • 提供全局导出接口 window.CanvasRenderer,供其他模块调用绘制、刷新、布局切换等功能。

3.6 页面逻辑

页面逻辑主要关注各个页面之间的跳转,不同页面之前的跳转逻辑基本是顺序执行,订单页面独立于主要页面之外,可以从任意界面点进后返回该界面,该部分处理模仿目前主流电影选座网站,方便用户即时查看所有订单信息。

  • petri网版本页面逻辑图

除了上述这些点击界面上的按钮的跳转功能,用户还可以点击页首的状态栏直接进行跳转,如下图所示,每个页面均可以跳转至之前的全部页面,而无法跳转至后序页面,保证了购票流程的正确性。

3.7 交互设计

3.7.1 交互系统架构

本项目的交互系统采用"状态中心化"设计,以stateManager.js为核心构建了完整的交互控制体系:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Canvas    │ ←→ │ StateManager│ ←→ │ CinemaData  │
└─────────────┘    └─────────────┘    └─────────────┘
      ↑                   ↑                   ↑
      │                   │                   │
      ↓                   ↓                   ↓
┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Renderer  │    │ User Input  │    │ Data Storage│
└─────────────┘    └─────────────┘    └─────────────┘

stateManager.js采用单一全局状态对象管理所有交互状态:

let globalState = {
    canvasElement: null,      // Canvas DOM元素
    canvasRect: null,         // Canvas位置信息
    hoveredSeat: null,        // 当前悬停座位
    isCtrlPressed: false,     // 键盘控制状态
    isInitialized: false      // 初始化标志
};

3.7.2 鼠标交互流程

  • 鼠标移动检测:通过handleCanvasMouseMove实时计算鼠标位置
  • 命中检测performSeatHitDetection计算鼠标与座位的几何关系
  • 状态更新:更新hoveredSeat并触发重绘
  • 视觉反馈CanvasRenderer 根据悬停状态调整座位渲染效果

3.7.3 点击选择流程

  • 点击事件处理handleCanvasClick捕获点击事件
  • 模式判断:根据isCtrlPressed判断单选/多选模式
  • 座位状态切换:调用selectSeat/deselectSeat修改状态
  • 全局通知:通过notifySelectionChange更新UI和订单信息

3.7.4 键盘控制机制

  • 多选模式:通过Ctrl/Command键切换多选状态
  • 事件监听handleKeyDownhandleKeyUp实时更新isCtrlPressed状态
  • 视觉提示:界面显示当前选择模式提示

四、遇到问题及解决办法

问题一:座位“选中”状态设计问题

  • 问题描述:关于核心模块 main.js 中,座位数据结构 seat 中的状态是否需要 selected
  • 原因分析:起初,我们认为核心模块 main.js 中的座位状态中,不需要有 selected 这一表示“座位被选择”的状态,因为这是与用户交互的过程中一个临时的状态,只起到选中的显示效果,不是座位的核心状态;此外,座位的其他状态(售出、预订、可用)都是仅由核心模块 main.js 修改的,其他模块中不会对这些状态进行改变,而 selected 需要在其他模块中进行设置。但在后续的代码编写过程中,我们发现,在保存与读取影院选座状态、进行自动选座、进行购买或预订等操作时,都需要座位的 selected 状态,故又将此状态设计进核心模块的座位状态中。
  • 解决方法:在 main.jsseat 的状态中,加入 selected 这一状态,并编写相应的接口提供给其他模块交互。

问题二:团体选座失效问题

  • 问题描述:当团体人数大于一排的座位数时,团体自动选座算法失效
  • 原因分析:第一版的团体自动选座算法,仅考虑了团体人数小于等于一排的座位数的情况,直接搜索满足条件的某一排座位
  • 解决方法:增加考虑团体人数大于一排的座位数时的代码,设计为选择连续的 $n$ 排,并且前 $n-1$ 排满员,最后一排的座位也需要连着

问题三:ui.js 模块代码过多

  • 问题描述:一开始,ui.js 模块代码过多,将近3000行,阅读起来较困难
  • 原因分析:最初 ui.js 模块设计为所有与 ui 相关的代码,此部分设计内容较多
  • 解决方法:按功能,将 ui.js 模块拆分为了 ui-coreui-memberui-movieui-ordersui-paymentui-validationui-view 模块

问题四:中心区域对齐问题

  • 问题描述:座位的中心区域的绘制有错位的问题
  • 原因分析:因为需要根据座位总列数为基数还是偶数来微调座位整体角度以保证居中,而中心区域起初是画出的扇形区域的正中间部分,因此无法适应座位变化。
  • 解决方法:计算出中心座位的相应编号,在座位图中画出四个点,首先连接两条纵向边,再根据半径

问题五:座位状态信息存储优化

  • 问题描述:座位状态存储有问题。
  • 原因分析:在一开始设计的座位信息只在main.js中的cinemaSeat之中存储,完全没有考虑到不同电影以及不同大小影厅都需要对应一个独立的影院的问题,因此需要对每一个特定大小以及对应电影的影厅进行单独存储,加入了根据Key在localStorage中存取的机制。加入这个机制之后又因为之前的实现的初始化比较随意,各个页面切换都有重新初始化,存在覆盖的问题,所以座位状态的更新仍存在较大问题。
  • 解决方法:检查所有影院初始化函数,仅保留一个初始化函数initializeCinemaSeats,且仅在进入选座界面的那个契机进行唯一的调用,具体初始化方式在前面影院座位状态数据中已经提到。后续的座位状态更新在所有订单操作后通过唯一可更改localStoragesaveCurrentCinemaState()函数将更改过的cinemaSeats存入localStorage

问题六:订单操作更改座位状态信息问题

  • 问题描述:在某天merge后突然发现购买后如果点击刷新网页的话canvas中所有的座位都会还原为available状态,但订单信息都还在,查看localStorage发现其中的对应座位信息也都全部被刷新了。
  • 原因分析:因为前面已经尽量控制过只有saveCurrentCinemaState()函数中对localStorage进行更改,其他函数只有调用这个函数才能引起localStorage变化。那么在刷新后就会被触发的函数只有时时刻刻都在检查的检查预定订单是否过期的checkAndReleaseExpiredReservations()函数,因为在网站初始化时还没有任何影院信息,canvas函数会先默认初始化一个10x20的空白影厅,此时的影厅所有座位均为可选择状态。而checkAndReleaseExpiredReservations()函数在检查后调用saveCurrentCinemaState()函数将更改过的cinemaSeats存入localStorage。但此时的cinemaSeats为默认空影厅,因此原本的影厅被覆盖了。
  • 解决方法:从这个问题我们意识到存入localStorage的方式也存在漏洞,之前的saveCurrentCinemaState()传入的是当前main.js中存;储的cinemaSeats,这个逻辑在购买和预定时不存在问题,但是其他的取消预订、支付预定、退款等订单操作可能需要更改当前main.js所表示的影厅之外的影厅,因此需要在订单操作中加入影厅的具体信息,根据电影和对应影院的信息得到Key去查找localStorage中的对应座位状态并更改。

问题七:订单详情无法居中问题

  • 问题描述:订单页面订单的详情预览显示位置有偏差,位于整个订单页面的中央而非视图的中央。

  • 原因分析:html中元素层级存在问题,由于详情页面的位置是相对于整个订单页面的,而不是相对于视图的,因此在订单页面中居中显示时,详情预览会偏离视图中心。

  • 解决方法:调整详情预览的定位方式,把订单详情模态框设置为全局模态框,其他保持不变,再通过css中align-items: center;等设置使其相对于视图进行居中对齐。

问题八:Canvas无法嵌入问题

  • 问题描述:Canvas模块写好后,在界面上依旧无法点击。

  • 原因分析:StateManager没有正确初始化,未能与IDcinema-canvas的影厅元素绑定,因此未能成功在Canvas上添加鼠标点击事件监听器。

  • 解决方法:通过绑定Canvas元素StateManager 与ID为 cinema-canvas 的Canvas元素关联,同时建立点击监听在Canvas上添加鼠标点击事件监听器,最终初始化座位状态管理,准备座位选择、状态跟踪等功能

setTimeout(() => {
        if (window.StateManager && window.StateManager.initializeStateManager) {
            window.StateManager.initializeStateManager('cinema-canvas');
            console.log('StateManager已初始化 - Canvas现在可以点击了');
        } else {
            console.error('StateManager模块未加载或initializeStateManager方法不存在');
        }
    }, 200); // 延迟确保Canvas已经创建

问题九:页面canvas尺寸错误问题

  • 问题描述:选座界面canvas尺寸过短,显示异常。

  • 原因分析:canvas内部的圆心以及半径设置有误,以及外围的控件大小不对。

  • 解决方法:调整canvas内部的设置值以及对应的css内部的weightheight保证canvas位于正确的位置。

问题十:多选无法刷新问题

  • 问题描述:Ctrl多选不在"已选座位"中显示,也无法激活"预定座位/直接购票",只有再单击一下时才会刷新
  • 原因分析:多选操作后未及时触发状态更新通知,导致UI未同步最新选中状态。
  • 解决方法:在handleCanvasClick函数中确保多选操作后调用notifySelectionChange(),并优化refreshCinemaDisplay()的调用时机。

问题十一:从支付界面返回选座界面后后无法取消选中座位

  • 问题描述:从支付界面返回后无法取消选中座位,已选座位显示会更新但canvas仍显示为选中状态。
  • 原因分析:页面返回时状态管理器未完全重新初始化,导致Canvas渲染状态与数据状态不同步。
  • 解决方法:在页面显示/隐藏事件中添加状态重置逻辑,确保返回时调用resetStateManager()refreshCinemaDisplay()

问题十二:鼠标坐标转换精度问题

  • 问题描述:在高分辨率屏幕上鼠标悬停检测不准确。
  • 原因分析:getMousePosition函数未考虑Canvas逻辑尺寸与CSS显示尺寸的比例差异。
  • 解决方法:在坐标转换时加入比例计算,通过scaleXscaleY参数精确映射鼠标位置到Canvas坐标。

问题十三:多选按钮仅支持Windows系统

  • 问题描述:多选功能在Mac系统上无法使用Command键触发。
  • 原因分析:键盘事件处理中只检测了Ctrl键,未考虑Mac的Command键(Meta键)。
  • 解决方法:在handleKeyDownhandleKeyUp中同时检测ControlMeta键,更新INTERACTION_CONFIG常量包含两种按键配置。

五、总结与感悟

  • 停云:

    作为组长,非常庆幸能遇到这么好的队友们。我在项目开始时就制定好了合作形式,以飞书共享文档为开发文档,git进行版本控制与成员合作,制定了分支的规范和提交规范,但其实,我在这之前对这几个工具的使用经验都没有那么多,在开发初期大家投入了一定时间学习使用后,便能非常默契的配合了。第一次多人使用git协作,与原本自己的线性提交相比,多人的提交记录颇有一种“分久必合,合久必分”的气势,五彩的分支曲线看起来赏心悦目,非常让人有成就感。感觉与之前的单人大作业很不相同,单人大作业的设计思路全部在自己的脑中,可以直接写代码,而多人合作大作业,不仅仅是要写代码,更需要持续的沟通、讨论与设计,将要写的部分代码的思路整理清晰后,写代码是较为水到渠成的事情。本次大作业的开发中,我们组达成了3人线下、1人线上的合作成就,很多个下午大家一起坐在C楼写代码与讨论,度过了非常愉快的一次开发!期待与大家的下次合作~

  • 六块:

    这是我第一次进行小组协作完成一次大作业,也是第一次使用git协作,感谢sigmal同志为我提供了git协作教程也感谢停云同志为整个小组制定了规范的提交格式。整个团队的协作还是很奇妙的,大家一起商议一些功能具体实现,把工作拆开再合起来的确是一门艺术,这一过程中我觉得明确接口、规范调用、以及协调好调用时机是比较重要的,另外也需要及时开会沟通同步进度和一些问题。这一过程中由于我负责的一直都是js文件,所以主要是熟练了javascript相关的应用,也学习了canvas技术的一些知识以及应用。

  • 五粮液:

    作为本次项目的前端UI部分负责人,在开发过程中也是十分深刻地体会到了git的美妙之处以及前后端之间的联系,在此要特别鸣谢我的三个队友,开发过程中不仅特别负责地写完了逻辑部分,还帮我de了好多前端的bug。通过这次作业我对HTMLCSSJavaScript等前端技术有了更深入的理解和应用,同时也学会了如何更好地利用Github与飞书进行团队协作,希望这次的项目能为后续的合作打下坚实的基础,顺便展望下一个与大家一起幸福写代码的学期~

  • sigmal:

    这次前端开发是我第二次使用git多人协作进行开发了(第一次是在科协写THUAI8的时候),虽然已经和git打交道了很长时间,现在还是会为git的智能和强大所震撼到。如果没有统一的git分支和提交规范,很难想象我们能够在4个人全天开发的情况下快速无冲突推进项目的进展。这次前端项目虽然只是一次小学期,但是我们小组的所有人都是以最大的努力和热情去做的,最终的代码量也是达到了12000行之多,可以说是全方位地锻炼了我的团队合作和复杂项目处理的能力。也希望这一次宝贵的合作经验可以为下学期的软工课程大作业打下基础。

六、分工

  • 停云:队长,项目启动时负责制定合作方式、分工与进度安排;开发前期负责设计 main.js 中的核心数据结构与算法(自动选座、票务操作);后续开发中负责各模块交互设计、系统流程梳理设计、各关键状态的状态转移设计、协调开发进度,进行深度测试找bug与debug,主要聚焦在影院座位状态的交互与刷新、页面切换的逻辑、增加选座验证、购票流程的实现、拆分 ui.js 为数个部分、设计并实现订单信息的存储与读取,涉及 main.jsstateManager.jsui-validationui-paymentui-core 等与 ui 相关的全部 js 文件。
  • 六块canvas.js 的编写,logo以及座位贴图的绘制,后续完成 canvas.js 后将一个 css 文件拆分为了10个部分;持续处理大小bug,主要聚焦在多页面多订单交互过程中的信息数据传递以及存取方面的问题。
  • 五粮液:主要负责编写前端UI相关代码,包括页面布局、样式设计和用户交互逻辑,搭建项目的整体HTML结构和CSS样式,同时实现界面UI的美化与交互效果,处理界面的所有点击事件并完成与 Javascript 逻辑部分的对接。主要负责文件有:index.html、全部css文件以及与ui相关的全部js文件。
  • sigmal:网页开发前期负责鼠标点击事件的交互逻辑与座位状态管理(详见stateManager.js);后续开发过程当中还负责订单界面的美化(详见ui-orders.js以及orders.css)、各部分代码简化重构和欢迎界面(hello.html以及hello.css)的制作。

七、使用的工具链

参考资料

About

清华大学软件学院2025夏季学期《Web前端技术实训》课程,三四五组大作业:电影院选座

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •