拖动代码
- /**
- * 获取点击或触摸事件对应的座位位置
- * 通过事件对象获取座位的行列信息
- * @param {Event|TouchEvent} event - 点击或触摸事件对象
- * @returns {Object} 返回座位位置对象,包含行(row)和列(col)信息,若未找到有效位置则返回 {row: -1, col: -1}
- */
- getSeatPosition(event) {
- // 统一处理触摸事件和点击事件
- // 触摸事件时从 touches 数组获取第一个触摸点
- // 点击事件时直接使用事件对象
- const touch = event.touches ? event.touches[0] : event;
- // 获取触摸/点击的坐标位置
- // clientX/Y 用于标准事件,x/y 用于某些特殊环境
- const x = touch.clientX || touch.x;
- const y = touch.clientY || touch.y;
- // 创建查询对象,用于获取 DOM 信息(当前未使用)
- const query = uni.createSelectorQuery();
- // 从事件目标的数据集中获取座位信息
- // 使用 HTML5 data-* 属性存储的行列信息
- if (event.target && event.target.dataset) {
- const dataset = event.target.dataset;
- // 检查数据集中是否包含有效的行列信息
- if (dataset.row !== undefined && dataset.col !== undefined) {
- // 返回解析后的座位位置
- // parseInt 确保返回数值类型
- return {
- row: parseInt(dataset.row),
- col: parseInt(dataset.col)
- };
- }
- }
- // 如果无法获取有效的座位信息
- // 返回表示无效位置的对象
- return { row: -1, col: -1 };
- },
- /**
- * 处理触摸开始事件
- * 用于初始化拖拽和缩放的起始状态
- * @param {TouchEvent} event - 触摸事件对象,包含触摸点信息
- */
- onTouchStart(event) {
- // 记录触摸开始的时间戳,用于后续判断是点击还是拖动
- this.touchStartTime = Date.now();
- // 重置移动标志,初始状态下未发生移动
- this.isMoved = false;
- // 单指触摸 - 处理拖动初始化
- if (event.touches.length === 1) {
- const touch = event.touches[0];
- // 记录当前触摸点作为上一次触摸位置,用于计算移动距离
- this.lastTouch = { x: touch.clientX, y: touch.clientY };
- // 记录触摸起始位置,用于计算总移动距离
- this.touchStartPos = { x: touch.clientX, y: touch.clientY };
- }
- // 双指触摸 - 处理缩放初始化
- else if (event.touches.length === 2) {
- // 计算两个触摸点之间的初始距离,用于后续计算缩放比例
- this.startDistance = this.getDistance(event.touches[0], event.touches[1]);
- }
- },
- // 处理触摸移动事件
- onTouchMove(event) {
- // 标记已经发生移动,用于区分点击和拖动
- this.isMoved = true;
- // 单指触摸 - 处理拖动
- if (event.touches.length === 1) {
- const touch = event.touches[0];
- // 计算相对于上一次触摸位置的偏移量
- const deltaX = touch.clientX - this.lastTouch.x;
- const deltaY = touch.clientY - this.lastTouch.y;
- // 根据当前缩放比例调整位移距离
- // 缩放比例越大,移动距离越小,保证移动体验一致
- this.position.x += deltaX / this.scale;
- this.position.y += deltaY / this.scale;
- // 更新最后一次触摸位置
- this.lastTouch = { x: touch.clientX, y: touch.clientY };
- }
- // 双指触摸 - 处理缩放
- else if (event.touches.length === 2) {
- // 计算当前两个触摸点之间的距离
- const currentDistance = this.getDistance(event.touches[0], event.touches[1]);
- // 根据距离变化计算新的缩放比例
- let newScale = this.scale * (currentDistance / this.startDistance);
- // 限制缩放范围在 minScale 和 maxScale 之间
- newScale = Math.max(this.minScale, Math.min(this.maxScale, newScale));
- this.scale = newScale;
- // 更新起始距离,用于下一次计算
- this.startDistance = currentDistance;
- }
- // 检查并限制移动边界,防止内容移出可视区域
- this.checkBoundaries();
- },
- // 处理手势结束
- onTouchEnd() {
- // 可以在这里处理手势结束后的逻辑
- },
- /**
- * 计算两个触摸点之间的距离
- * @param {Object} touch1 - 第一个触摸点,包含 clientX 和 clientY 坐标
- * @param {Object} touch2 - 第二个触摸点,包含 clientX 和 clientY 坐标
- * @returns {number} 两点之间的欧几里得距离
- */
- getDistance(touch1, touch2) {
- // 计算 X 轴方向的距离差
- const dx = touch1.clientX - touch2.clientX;
- // 计算 Y 轴方向的距离差
- const dy = touch1.clientY - touch2.clientY;
- // 使用勾股定理计算两点之间的直线距离
- // distance = √(dx² + dy²)
- return Math.sqrt(dx * dx + dy * dy);
- },
- /**
- * 检查并限制座位区域的移动边界
- * 防止用户将座位区域拖动到视图之外
- */
- checkBoundaries() {
- // 定义最大可移动距离(像素)
- const maxX = 200; // X轴最大移动距离,可根据实际座位区域大小调整
- const maxY = 200; // Y轴最大移动距离,可根据实际座位区域大小调整
- // 限制X轴移动范围:[-maxX, maxX]
- // Math.min 确保不会超过右边界
- // Math.max 确保不会超过左边界
- this.position.x = Math.max(-maxX, Math.min(maxX, this.position.x));
- // 限制Y轴移动范围:[-maxY, maxY]
- // Math.min 确保不会超过下边界
- // Math.max 确保不会超过上边界
- this.position.y = Math.max(-maxY, Math.min(maxY, this.position.y));
- },
复制代码 盘算总价代码
- /**
- * 获取指定座位在所有已选座位中的序号
- * @param {number} row - 要查询的座位行号
- * @param {number} col - 要查询的座位列号
- * @returns {number} 返回该座位是第几个被选中的座位(从1开始计数)
- *
- * 使用场景:
- * 1. 用于确定座位的选中顺序
- * 2. 可用于显示座位的选中序号
- * 3. 帮助用户了解座位的选择顺序
- */
- getSelectedIndex(row, col) {
- // 初始化计数器,从1开始计数
- let count = 1;
- // 遍历所有座位
- for (let i = 0; i < this.seatMap.length; i++) {
- for (let j = 0; j < this.seatMap[i].length; j++) {
- // 检查当前遍历到的座位是否被选中
- if (this.seatMap[i][j].selected) {
- // 如果找到目标座位,返回当前计数
- if (i === row && j === col) return count;
- // 如果不是目标座位,计数器加1
- count++;
- }
- }
- }
- return count;
- },
- // 获取已选座位列表
- getSelectedSeats() {
- const selectedSeats = [];
- this.seatMap.forEach((row, rowIndex) => {
- row.forEach((seat, colIndex) => {
- if (seat.selected) {
- selectedSeats.push({
- row: rowIndex,
- col: colIndex,
- type: seat.type
- });
- }
- });
- });
- return selectedSeats;
- },
- /**
- * 计算所有已选座位的总价
- * @returns {string} 返回格式化后的总价字符串,保留两位小数
- *
- * 使用场景:
- * 1. 显示确认选座按钮上的总价
- * 2. 提交订单时计算支付金额
- * 3. 更新用户选座时实时显示价格
- */
- getTotalPrice() {
- // 定义不同类型座位的价格映射
- const prices = {
- pink: 40, // 粉色座位(VIP座)价格
- orange: 38, // 橙色座位(情侣座)价格
- blue: 35 // 蓝色座位(普通座)价格
- };
- // 使用 reduce 方法计算总价
- // 1. 获取所有已选座位列表
- // 2. 根据每个座位的类型获取对应价格
- // 3. 累加所有座位的价格
- return this.getSelectedSeats().reduce((total, seat) => {
- // total: 累计总价
- // seat: 当前座位信息,包含 type 属性
- return total + prices[seat.type];
- }, 0).toFixed(2); // 初始值为0,结果保留两位小数
- },
- /**
- * 获取指定类型座位的单价
- * @param {string} type - 座位类型('pink'|'orange'|'blue')
- * @returns {string} 返回格式化后的价格字符串,保留两位小数
- *
- * 使用场景:
- * 1. 显示单个座位的价格
- * 2. 在已选座位列表中显示每个座位的单价
- */
- getSeatPrice(type) {
- // 定义不同类型座位的价格映射
- const prices = {
- pink: 40, // 粉色座位(VIP座)价格
- orange: 38, // 橙色座位(情侣座)价格
- blue: 35 // 蓝色座位(普通座)价格
- };
- // 返回格式化后的价格,保留两位小数
- return prices[type].toFixed(2);
- },
- /**
- * 处理确认选座操作
- * 验证选座状态并进行后续处理
- *
- * 使用场景:
- * 1. 用户点击确认选座按钮时触发
- * 2. 验证是否已选择座位
- * 3. 进行下一步订单处理
- */
- confirmSeats() {
- // 检查是否有选中的座位
- if (this.selectedSeatsCount === 0) {
- // 如果没有选择座位,显示提示信息
- uni.showToast({
- title: '请先选择座位',
- icon: 'none'
- });
- return;
- }
- // TODO: 处理确认选座逻辑
- // 可以添加以下操作:
- // 1. 获取选中的座位信息
- // 2. 调用后端API锁定座位
- // 3. 跳转到订单确认页面
- // 4. 处理支付流程等
- console.log('确认选座', this.getSelectedSeats());
- }
复制代码 完备代码
- <template> <view class="chooseSeat"> <!-- 价格说明 --> <view class="price-info"> <view class="price-item"> <view class="price-box pink"></view> <text>¥40.00</text> </view> <view class="price-item"> <view class="price-box orange"></view> <text>¥38.00</text> </view> <view class="price-item"> <view class="price-box blue"></view> <text>¥35.00</text> </view> </view> <!-- 银幕 --> <view class="screen"> <image class="screen-image" src="https://s.xitupt.com/tsimgs/949558333604714792/20250318/h5_mng_1742302489261" mode="aspectFit"></image> <!-- <view class="screen-text"> <text>IMAX</text> <text>4DX</text> </view> --> </view> <!-- 座位区域 --> <view class="seat-container"> <!-- 修改行号部门,让它和座位区域一起缩放移动 --> <view class="seat-area-wrapper" :style="{ transform: `scale(${scale}) translate(${position.x}px, ${position.y}px)`, transformOrigin: '0 0' }" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"> <!-- 行号 --> <view class="row-numbers"> <view v-for="i in 6" :key="i" class="row-number">{{ i }}</view> </view> <!-- 座位图 --> <view class="seats-area"> <view v-for="(row, rowIndex) in seatMap" :key="rowIndex" class="seat-row"> <view v-for="(seat, colIndex) in row" :key="colIndex" class="seat" :class="[ seat.type, { 'selected': seat.selected, 'sold': seat.sold } ]" :data-row="rowIndex" :data-col="colIndex" @tap.stop="selectSeat(rowIndex, colIndex)"> <image v-if="seat.selected" class="seat-selected-image" src="https://s.xitupt.com/tsimgs/949558333604714792/20250318/h5_mng_1742303462702" mode="aspectFit"> </image> </view> </view> </view> </view> </view> <!-- 底部固定区域 --> <view class="bottom-fixed"> <!-- 卡片部门 --> <view class="info-card"> <view class="movie-info"> <view class="movie-title"> <text class="title">初步举证</text> </view> <view class="movie-time"> <text class="today">今天</text> <text class="time">14:10-16:25</text> </view> <!-- 已选座位区域 --> <view class="selected-seats" v-if="selectedSeatsCount > 0"> <!-- 已选标签和数量 --> <view class="selected-header"> <text class="selected-label">已选:</text> <text class="selected-count">{{ selectedSeatsCount }}个座位</text> </view> <!-- 座位详情列表 --> <scroll-view class="seats-scroll" scroll-x show-scrollbar="false"> <view class="seats-container"> <view class="seat-tag" v-for="(seat, index) in getSelectedSeats()" :key="index"> <view class="seat-info"> <view class="seat-position">{{ `${seat.row + 1}排${seat.col + 1}座` }}</view> <view class="seat-price">¥{{ getSeatPrice(seat.type) }}</view> </view> <text class="close" @tap.stop="selectSeat(seat.row, seat.col)">×</text> </view> </view> </scroll-view> </view> </view> </view> <!-- 按钮部门 --> <view class="button-wrapper"> <view class="confirm-button" :class="{ 'disabled': selectedSeatsCount === 0 }" @tap="confirmSeats"> <text>¥{{ getTotalPrice() }} 确认选座</text> </view> </view> </view> </view></template><script>export default { data() { return { seatMap: [], // 座位数据 isDragging: false, // 是否正在拖动中 dragAction: false, // 拖动动作(true:选择, false:取消选择) lastDragPosition: { row: -1, col: -1 }, // 上一次拖动的位置 scale: 1, // 当前缩放比例 startDistance: 0, // 开始的手势距离 position: { x: 0, y: 0 }, // 添加位置信息 lastTouch: { x: 0, y: 0 }, // 记载前次触摸位置 minScale: 0.5, // 最小缩放比例 maxScale: 2, // 最大缩放比例 maxSelectedSeats: 40, // 最多可选座位数 selectedSeatsCount: 0, // 当前已选座位数 touchStartTime: 0, // 触摸开始的时间戳,用于区分点击和拖动 touchStartPos: { x: 0, y: 0 }, // 触摸开始的位置,用于盘算移动距离 isMoved: false, // 是否发生了移动,用于区分点击和拖动事件 } }, created() { this.initSeatMap() }, methods: { /** * 初始化座位图数据 * 创建一个二维数组来表示影院座位布局 */ initSeatMap() { // 定义座位图的尺寸 const rows = 6 // 总行数 const cols = 8 // 每行座位数 // 创建二维数组并初始化每个座位的属性 this.seatMap = Array(rows).fill().map((_, rowIndex) => Array(cols).fill().map((_, colIndex) => ({ // 根据位置确定座位类型(粉色/橙色/蓝色) type: this.getSeatType(rowIndex, colIndex), // 初始状态为未选中 selected: false, // 随机设置座位是否已售出 sold: this.getRandomSoldStatus(rowIndex, colIndex) })) ) }, getSeatType(row, col) { // 第一列和第二列、倒数第二列、倒数第一列是蓝色 if (col < 2 || col > 5) { return 'blue' } // 第三列和倒数第三列是橙色 if (col === 2 || col === 5) { return 'orange' } // 第一行的第四第五列是橙色 if (row === 0 && (col === 3 || col === 4)) { return 'orange' } // 第五行的第四第五列是橙色 if (row === 5 && (col === 3 || col === 4)) { return 'orange' } // 第四第五列的第二到第四行是粉色 if ((col === 3 || col === 4) && (row >= 1 && row <= 4)) { return 'pink' } return 'blue' // 默认蓝色 }, /** * 处理座位选择事件 * 用于切换座位的选中状态,并管理已选座位数量 * @param {number} row - 座位所在行号 * @param {number} col - 座位所在列号 */ selectSeat(row, col) { // 防止拖动操纵触发选座 // 当用户拖动检察座位时,不应触发选座操纵 if (this.isMoved) return; // 检查座位是否可选 // 验证座位是否存在且未售出 if (!this.isSeatSelectable(row, col)) return; // 获取目的座位对象 const seat = this.seatMap[row][col]; // 检查是否超出最大可选座位数 // 仅在要选中新座位时举行检查 if (!seat.selected && this.selectedSeatsCount >= this.maxSelectedSeats) { // 显示提示信息 uni.showToast({ title: `最多只能选择${this.maxSelectedSeats}个座位`, icon: 'none' }); return; } // 切换座位选中状态 seat.selected = !seat.selected; // 更新已选座位计数 // 选中时 +1,取消选中时 -1 this.selectedSeatsCount += seat.selected ? 1 : -1; }, // 随机设置部门座位为已售 getRandomSoldStatus(row, col) { // 约20%的概率将座位标记为已售 return Math.random() < 0.2; }, // 检查座位是否可选择 isSeatSelectable(row, col) { // 确保座位存在且未售出 return this.seatMap[row] && this.seatMap[row][col] && !this.seatMap[row][col].sold; }, /**
- * 获取点击或触摸事件对应的座位位置
- * 通过事件对象获取座位的行列信息
- * @param {Event|TouchEvent} event - 点击或触摸事件对象
- * @returns {Object} 返回座位位置对象,包含行(row)和列(col)信息,若未找到有效位置则返回 {row: -1, col: -1}
- */
- getSeatPosition(event) {
- // 统一处理触摸事件和点击事件
- // 触摸事件时从 touches 数组获取第一个触摸点
- // 点击事件时直接使用事件对象
- const touch = event.touches ? event.touches[0] : event;
- // 获取触摸/点击的坐标位置
- // clientX/Y 用于标准事件,x/y 用于某些特殊环境
- const x = touch.clientX || touch.x;
- const y = touch.clientY || touch.y;
- // 创建查询对象,用于获取 DOM 信息(当前未使用)
- const query = uni.createSelectorQuery();
- // 从事件目标的数据集中获取座位信息
- // 使用 HTML5 data-* 属性存储的行列信息
- if (event.target && event.target.dataset) {
- const dataset = event.target.dataset;
- // 检查数据集中是否包含有效的行列信息
- if (dataset.row !== undefined && dataset.col !== undefined) {
- // 返回解析后的座位位置
- // parseInt 确保返回数值类型
- return {
- row: parseInt(dataset.row),
- col: parseInt(dataset.col)
- };
- }
- }
- // 如果无法获取有效的座位信息
- // 返回表示无效位置的对象
- return { row: -1, col: -1 };
- },
- /**
- * 处理触摸开始事件
- * 用于初始化拖拽和缩放的起始状态
- * @param {TouchEvent} event - 触摸事件对象,包含触摸点信息
- */
- onTouchStart(event) {
- // 记录触摸开始的时间戳,用于后续判断是点击还是拖动
- this.touchStartTime = Date.now();
- // 重置移动标志,初始状态下未发生移动
- this.isMoved = false;
- // 单指触摸 - 处理拖动初始化
- if (event.touches.length === 1) {
- const touch = event.touches[0];
- // 记录当前触摸点作为上一次触摸位置,用于计算移动距离
- this.lastTouch = { x: touch.clientX, y: touch.clientY };
- // 记录触摸起始位置,用于计算总移动距离
- this.touchStartPos = { x: touch.clientX, y: touch.clientY };
- }
- // 双指触摸 - 处理缩放初始化
- else if (event.touches.length === 2) {
- // 计算两个触摸点之间的初始距离,用于后续计算缩放比例
- this.startDistance = this.getDistance(event.touches[0], event.touches[1]);
- }
- },
- // 处理触摸移动事件
- onTouchMove(event) {
- // 标记已经发生移动,用于区分点击和拖动
- this.isMoved = true;
- // 单指触摸 - 处理拖动
- if (event.touches.length === 1) {
- const touch = event.touches[0];
- // 计算相对于上一次触摸位置的偏移量
- const deltaX = touch.clientX - this.lastTouch.x;
- const deltaY = touch.clientY - this.lastTouch.y;
- // 根据当前缩放比例调整位移距离
- // 缩放比例越大,移动距离越小,保证移动体验一致
- this.position.x += deltaX / this.scale;
- this.position.y += deltaY / this.scale;
- // 更新最后一次触摸位置
- this.lastTouch = { x: touch.clientX, y: touch.clientY };
- }
- // 双指触摸 - 处理缩放
- else if (event.touches.length === 2) {
- // 计算当前两个触摸点之间的距离
- const currentDistance = this.getDistance(event.touches[0], event.touches[1]);
- // 根据距离变化计算新的缩放比例
- let newScale = this.scale * (currentDistance / this.startDistance);
- // 限制缩放范围在 minScale 和 maxScale 之间
- newScale = Math.max(this.minScale, Math.min(this.maxScale, newScale));
- this.scale = newScale;
- // 更新起始距离,用于下一次计算
- this.startDistance = currentDistance;
- }
- // 检查并限制移动边界,防止内容移出可视区域
- this.checkBoundaries();
- },
- // 处理手势结束
- onTouchEnd() {
- // 可以在这里处理手势结束后的逻辑
- },
- /**
- * 计算两个触摸点之间的距离
- * @param {Object} touch1 - 第一个触摸点,包含 clientX 和 clientY 坐标
- * @param {Object} touch2 - 第二个触摸点,包含 clientX 和 clientY 坐标
- * @returns {number} 两点之间的欧几里得距离
- */
- getDistance(touch1, touch2) {
- // 计算 X 轴方向的距离差
- const dx = touch1.clientX - touch2.clientX;
- // 计算 Y 轴方向的距离差
- const dy = touch1.clientY - touch2.clientY;
- // 使用勾股定理计算两点之间的直线距离
- // distance = √(dx² + dy²)
- return Math.sqrt(dx * dx + dy * dy);
- },
- /**
- * 检查并限制座位区域的移动边界
- * 防止用户将座位区域拖动到视图之外
- */
- checkBoundaries() {
- // 定义最大可移动距离(像素)
- const maxX = 200; // X轴最大移动距离,可根据实际座位区域大小调整
- const maxY = 200; // Y轴最大移动距离,可根据实际座位区域大小调整
- // 限制X轴移动范围:[-maxX, maxX]
- // Math.min 确保不会超过右边界
- // Math.max 确保不会超过左边界
- this.position.x = Math.max(-maxX, Math.min(maxX, this.position.x));
- // 限制Y轴移动范围:[-maxY, maxY]
- // Math.min 确保不会超过下边界
- // Math.max 确保不会超过上边界
- this.position.y = Math.max(-maxY, Math.min(maxY, this.position.y));
- }, /**
- * 获取指定座位在所有已选座位中的序号
- * @param {number} row - 要查询的座位行号
- * @param {number} col - 要查询的座位列号
- * @returns {number} 返回该座位是第几个被选中的座位(从1开始计数)
- *
- * 使用场景:
- * 1. 用于确定座位的选中顺序
- * 2. 可用于显示座位的选中序号
- * 3. 帮助用户了解座位的选择顺序
- */
- getSelectedIndex(row, col) {
- // 初始化计数器,从1开始计数
- let count = 1;
- // 遍历所有座位
- for (let i = 0; i < this.seatMap.length; i++) {
- for (let j = 0; j < this.seatMap[i].length; j++) {
- // 检查当前遍历到的座位是否被选中
- if (this.seatMap[i][j].selected) {
- // 如果找到目标座位,返回当前计数
- if (i === row && j === col) return count;
- // 如果不是目标座位,计数器加1
- count++;
- }
- }
- }
- return count;
- },
- // 获取已选座位列表
- getSelectedSeats() {
- const selectedSeats = [];
- this.seatMap.forEach((row, rowIndex) => {
- row.forEach((seat, colIndex) => {
- if (seat.selected) {
- selectedSeats.push({
- row: rowIndex,
- col: colIndex,
- type: seat.type
- });
- }
- });
- });
- return selectedSeats;
- },
- /**
- * 计算所有已选座位的总价
- * @returns {string} 返回格式化后的总价字符串,保留两位小数
- *
- * 使用场景:
- * 1. 显示确认选座按钮上的总价
- * 2. 提交订单时计算支付金额
- * 3. 更新用户选座时实时显示价格
- */
- getTotalPrice() {
- // 定义不同类型座位的价格映射
- const prices = {
- pink: 40, // 粉色座位(VIP座)价格
- orange: 38, // 橙色座位(情侣座)价格
- blue: 35 // 蓝色座位(普通座)价格
- };
- // 使用 reduce 方法计算总价
- // 1. 获取所有已选座位列表
- // 2. 根据每个座位的类型获取对应价格
- // 3. 累加所有座位的价格
- return this.getSelectedSeats().reduce((total, seat) => {
- // total: 累计总价
- // seat: 当前座位信息,包含 type 属性
- return total + prices[seat.type];
- }, 0).toFixed(2); // 初始值为0,结果保留两位小数
- },
- /**
- * 获取指定类型座位的单价
- * @param {string} type - 座位类型('pink'|'orange'|'blue')
- * @returns {string} 返回格式化后的价格字符串,保留两位小数
- *
- * 使用场景:
- * 1. 显示单个座位的价格
- * 2. 在已选座位列表中显示每个座位的单价
- */
- getSeatPrice(type) {
- // 定义不同类型座位的价格映射
- const prices = {
- pink: 40, // 粉色座位(VIP座)价格
- orange: 38, // 橙色座位(情侣座)价格
- blue: 35 // 蓝色座位(普通座)价格
- };
- // 返回格式化后的价格,保留两位小数
- return prices[type].toFixed(2);
- },
- /**
- * 处理确认选座操作
- * 验证选座状态并进行后续处理
- *
- * 使用场景:
- * 1. 用户点击确认选座按钮时触发
- * 2. 验证是否已选择座位
- * 3. 进行下一步订单处理
- */
- confirmSeats() {
- // 检查是否有选中的座位
- if (this.selectedSeatsCount === 0) {
- // 如果没有选择座位,显示提示信息
- uni.showToast({
- title: '请先选择座位',
- icon: 'none'
- });
- return;
- }
- // TODO: 处理确认选座逻辑
- // 可以添加以下操作:
- // 1. 获取选中的座位信息
- // 2. 调用后端API锁定座位
- // 3. 跳转到订单确认页面
- // 4. 处理支付流程等
- console.log('确认选座', this.getSelectedSeats());
- } }}</script><style scoped>.chooseSeat { width: 100%; min-height: 100vh; padding: 20rpx; box-sizing: border-box;}.chooseSeat-header { padding: 20rpx 0; text-align: center; font-size: 32rpx; font-weight: bold;}.price-info { display: flex; justify-content: space-around; margin: 20rpx 0;}.price-item { display: flex; align-items: center;}.price-box { width: 30rpx; height: 30rpx; margin-right: 10rpx; border-radius: 4rpx;}.price-box.pink { background-color: #FF3162;}.price-box.orange { background-color: #F6BB7F;}.price-box.blue { background-color: #8BBFF0;}.screen { margin: 40rpx 0; text-align: center;}.screen-image { width: 90%; height: 60rpx; margin: 0 auto 10rpx;}.seat-container { width: 100%; height: 100%; display: flex; margin-top: 40rpx; user-select: none; touch-action: none; /* overflow: hidden; */ /* 防止缩放时溢出 */}/* 新增包装器样式 */.seat-area-wrapper { display: flex; will-change: transform; touch-action: none;}/* 修改行号样式 */.row-numbers { width: 35rpx; margin-right: 75rpx; background: rgba(0, 0, 0, 0.3); border-radius: 36rpx; display: flex; flex-direction: column;}.row-number { height: 50rpx; /* 与座位高度一致 */ line-height: 50rpx; text-align: center; font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #FFFFFF; margin-bottom: 20rpx; /* 与座位间距一致 */}.row-number:last-child { margin-bottom: 0; /* 最后一个行号不须要底部间距 */}.seat-row { display: flex; justify-content: space-between; margin-bottom: 20rpx; /* 修改座位行间距为20rpx */}.seat-row:last-child { margin-bottom: 0; /* 最后一行不须要底部间距 */}.seat { width: 50rpx; height: 50rpx; margin-right: 20rpx; background-color: #fff; border-radius: 8rpx; position: relative; transition: transform 0.3s ease; /* 添加过渡效果 */}.seat:last-child { margin-right: 0; /* 最后一个座位不须要右边距 */}.seats-area { flex: 1; will-change: transform; touch-action: none; padding: 0; /* 移除内边距 */}/* 修改选中状态的样式 */.seat.selected { transform: scale(1.05); /* 规复放大效果 */}/* 选中图片样式 */.seat-selected-image { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none;}/* 修改选中状态的边框样式 */.seat.selected.pink { border: 2rpx solid #FF3162;}.seat.selected.orange { border: 2rpx solid #F6BB7F;}.seat.selected.blue { border: 2rpx solid #8BBFF0;}/* 未选中状态样式 */.seat.pink { border: 2rpx solid #FF3162;}.seat.orange { border: 2rpx solid #F6BB7F;}.seat.blue { border: 2rpx solid #8BBFF0;}/* 已售出座位的样式 */.seat.sold { background-color: #F5F5F5; border-color: #E0E0E0; opacity: 0.6; cursor: not-allowed; transition: all 0.2s ease; filter: grayscale(100%);}.seat.sold::after { content: "×"; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; color: #999;}/* 添加hover效果 */.seat:active:not(.sold) { opacity: 0.8; transform: scale(0.95);}/* 底部固定区域 */.bottom-fixed { position: fixed; left: 0; bottom: 0; width: 100%; z-index: 100; display: flex; flex-direction: column;}/* 卡片样式 */.info-card { margin: 20rpx; padding: 20rpx; background-color: #fff; border-radius: 16rpx; box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);}.movie-title { margin-bottom: 16rpx;}.movie-title .title { font-size: 32rpx; font-weight: bold; margin-right: 20rpx;}.movie-time { display: flex; align-items: center; margin-bottom: 16rpx;}.movie-time .today { font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #FF3162; margin-right: 8rpx;}.movie-time .time { font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #666666;}.selected-seats { display: flex; flex-direction: column; gap: 16rpx;}.selected-header { display: flex; align-items: center;}.selected-label { font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #666666;}.selected-count { font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #FF3162; margin-left: 8rpx;}/* 滚动区域样式 */.seats-scroll { width: 100%; white-space: nowrap; overflow: hidden;}/* 座位容器样式 */.seats-container { display: inline-flex; align-items: center;}.seat-tag { width: 147rpx; height: 64rpx; background: #F4F5F7; border-radius: 10rpx; display: inline-flex; align-items: center; justify-content: space-between; padding: 0 16rpx; margin-right: 10rpx; flex-shrink: 0;}.seat-info { display: flex; flex-direction: column; align-items: flex-start;}.seat-position { font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #666666;}.seat-price { font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 20rpx; color: #FF3162;}.seat-tag .close { color: #999; font-size: 28rpx;}.seat-tag:last-child { margin-right: 0;}/* 按钮容器 */.button-wrapper { padding: 20rpx; background-color: #fff;}/* 确认按钮样式 */.confirm-button { width: 100%; height: 88rpx; background: linear-gradient(to right, #ff3162, #ff6c89); border-radius: 44rpx; display: flex; justify-content: center; align-items: center; color: #fff; font-size: 32rpx; font-weight: 500; margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.2+ */ margin-bottom: env(safe-area-inset-bottom); /* iOS 11.2+ */}.confirm-button.disabled { background: #ccc; opacity: 0.8;}</style>
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |