更新记录

1.0.2(2026-09-03) 下载此版本

优化包大小

1.0.1(2026-09-03) 下载此版本

优化


平台兼容性

uni-app

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
× × - - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
× × × - × × - - -

其他

多语言 暗黑模式 宽屏模式
× ×

canvas-seatmap-pro

适用范围

  • 影院、剧院和规则行列排列的选座场景
  • 普通座、情侣座、分区价格
  • H5、微信小程序、支付宝小程序、抖音小程序(均已真机验证)
  • Vue 2、Vue 3
  • App 端(app-vue / nvue)暂不支持

本组件不处理座位设计器、任意旋转座位、自由多边形分区或大型场馆设计图格式。

Vue2 项目注意:监听 error 事件时处理方法不要命名为 onError(uni-app 应用级生命周期保留名,Vue2 下会被框架劫持导致绑定失效),改用其他名字如 onSeatmapError。

抖音小程序接入注意:抖音不支持 canvas 的 disable-scroll 属性,宿主页面需在 pages.json 配置 "disableScroll": true,否则拖动画布会带动页面滚动。

基础用法

组件符合 easycom 规范,无需 import 和注册,直接在页面中使用 <canvas-seatmap-pro> 标签即可。

<template>
  <canvas-seatmap-pro
    ref="seatmap"
    @ready="onReady"
    @change="onChange"
    @seat-tap="onSeatTap"
    @error="onSeatmapError"
  />
</template>

<script>
export default {
  data() {
    return { seatList: [], selectedSeats: [] }
  },
  async onReady() {
    try {
      await this.$refs.seatmap.init({
        seatList: this.seatList,
        options: {
          title: '淘淘票',
          hallName: '1号厅',
          footerHeight: 270,
          maxSelectNum: 4,
          isolateSeats: true,
          areaList: []
        }
      })
    } catch (error) {
      // 初始化错误也会通过 error 事件抛出。
    }
  },
  methods: {
    onReady(event) {
      console.log('初始化完成', event.seatCount)
    },
    onChange(event) {
      this.selectedSeats = event.selectedSeats
    },
    onSeatTap(event) {
      console.log(event.seat, event.changed)
    },
    onSeatmapError(error) {
      if (['MAX_SELECT_EXCEEDED', 'ISOLATED_SEAT'].includes(error.code) && error.message) {
        uni.showToast({ title: error.message, icon: 'none' })
        return
      }
      // 其他技术错误用于开发排查,不建议直接向终端用户展示。
      console.error('[canvas-seatmap-pro]', error)
    }
  }
}
</script>

组件采用 ref.init() 初始化,不使用 seatListoptions Props。

座位数据

[
  {
    seatId: 'seat-6-13',
    rowId: 6,
    columnId: 13,
    rowName: 'F排',
    seatName: 'F排13座',
    status: 1,
    seatType: 0,
    areaId: '372',
    marketPrice: 8000
  }
]
字段 说明
seatId 座位唯一标识,推荐所有座位都提供
rowId Canvas布局行坐标,必须是大于等于0的数字或数字字符串
columnId Canvas布局列坐标,必须是大于等于0的数字或数字字符串
rowName 接口返回的实际排号;未传时不显示排号栏
seatName 业务展示名称,组件原样返回
status 座位状态
seatType 座位类型
areaId 价格或颜色区域标识

行列坐标只决定座位画在哪里。接口缺少某个坐标时,该位置不绘制座位;组件不会将空缺自行解释为过道。

字段映射

接口字段名不同时,通过 SEAT_FIELDS 映射:

{
  SEAT_FIELDS: {
    ROW_ID: 'rowId',
    COLUMN_ID: 'columnId',
    ROW_NAME: 'rowName',
    SEAT_ID: 'seatId',
    STATUS_NAME: 'status',
    TYPE_NAME: 'seatType',
    AREA_ID: 'areaId'
  }
}

字段映射只指定接口字段位置,不生成排号、座号或座位名称。

状态与类型

{
  SEAT_STATUS: {
    DISABLED: -2,
    LOCKED: -1,
    SOLD: 0,
    AVAILABLE: 1,
    SELECTED: 10
  },
  SEAT_TYPE: {
    SINGLE: 0,
    COUPLE_LEFT: 1,
    COUPLE_MIDDLE: 2,
    COUPLE_RIGHT: 3
  }
}

情侣左座必须与右侧的 COUPLE_MIDDLECOUPLE_RIGHT 配对。情侣中/右座的左侧必须是 COUPLE_LEFT。异常接口数据会返回 INVALID_COUPLE_PAIR

配置

配置 类型 默认值 说明
title String '' 座位区域底部标题
hallName String '屏幕' 银幕名称
hallHeight Number 40 银幕区域高度
footerHeight Number 0 底部业务区域遮挡高度
maxSelectNum Number 0 最大选座数,0表示不限制
seatMaxWidth Number 35 最大座位宽度
seatMinWidth Number 5 最小座位宽度
isolateSeats Boolean true 是否启用孤座校验
autoResize Boolean true 是否监听窗口尺寸变化
canvasWidth Number 自动读取宿主容器宽度 宿主容器宽度。未传入时会自动跟随容器宽度;传入后保持固定
canvasHeight Number 自动填满窗口剩余高度 宿主容器高度。传入后不再被窗口高度覆盖
messages Object 见下文 最大选座数和孤座提示文案
areaList Array [] 分区颜色配置
SEAT_FIELDS Object 见上文 接口字段映射
SEAT_STATUS Object 见上文 状态值映射
SEAT_TYPE Object 见上文 类型值映射
SEAT_STYLE Object 见下文 状态样式

画布默认自动读取宿主容器宽度,并在 H5 容器宽度变化时自动重绘;画布高度和像素比由组件读取。嵌入固定高度的非全屏容器时,传入 canvasHeight;组件会按该尺寸计算初始全景、缩放边界和点击坐标。

await seatmap.init({
  seatList,
  options: {
    canvasWidth: 320,
    canvasHeight: 420,
    footerHeight: 80
  }
})

提示文案

组件只内置两类需要用户处理的规则提示,支持覆盖默认文案。MAX_SELECT_EXCEEDED 中可使用 {maxSelectNum} 占位符:

{
  messages: {
    MAX_SELECT_EXCEEDED: '一次最多购买{maxSelectNum}张票',
    ISOLATED_SEAT: '请不要留下单个空座'
  }
}

点击空白、不可用座位、选座成功和状态更新不会产生提示文案。程序化选座失败通过 code 和相关字段返回,由接入方决定是否展示。

分区颜色

{
  areaList: [
    {
      areaId: '372',
      areaName: 'VIP区',
      strokeStyle: '#97cafc',
      marketPrice: 8000
    }
  ]
}

areaList 中区域标识字段必须与 SEAT_FIELDS.AREA_ID 指向的字段一致。

状态样式

{
  SEAT_STYLE: {
    DISABLED: { fillStyle: '#f7f9fc', strokeStyle: '#eeeff1' },
    LOCKED: { fillStyle: '#f7f9fc', strokeStyle: '#eeeff1' },
    SOLD: { fillStyle: 'red', strokeStyle: 'transparent' },
    AVAILABLE: { fillStyle: 'transparent', strokeStyle: '#97cafc' },
    SELECTED: { fillStyle: '#0ed7b8', strokeStyle: 'transparent' }
  }
}

方法

初始化与尺寸

await seatmap.init({ seatList, options })
seatmap.destroy()
await seatmap.resize()
seatmap.resetView()

主动调整尺寸:

await seatmap.resize({
  canvasWidth: 375,
  canvasHeight: 600,
  footerHeight: 270
})

查询和选择

seatmap.getSelectedSeats()
seatmap.getSeat(seatId)
seatmap.selectSeats([seatId1, seatId2])
seatmap.cancelSeat(seatId)
seatmap.clearSelectedSeats()

cancelSeat() 也兼容传入组件事件返回的座位对象。 selectSeats() 不能执行时只返回结构化结果:不存在的座位使用 SEAT_NOT_FOUND,不可选座位使用 SEAT_UNAVAILABLE;这两种结果不包含用户提示文案。

更新接口数据

seatmap.updateSeatStatus('seat-101', 0)

seatmap.updateSeatStatuses([
  { seatId: 'seat-101', status: 0 },
  { seatId: 'seat-102', status: -1 }
])

seatmap.replaceSeatList(nextSeatList)

整体替换会重建索引并复位视图。组件内部使用座位数据副本,不修改调用方传入的原始对象。

规则校验

const result = seatmap.validateSelection()
{
  valid: false,
  violations: [
    {
      code: 'ISOLATED_SEAT',
      message: '请不要留下单个空座',
      seats: ['seat-101']
    }
  ]
}
规则码 说明
MAX_SELECT_EXCEEDED 超过最大选座数
COUPLE_SEAT_INCOMPLETE 情侣座没有成对选择
ISOLATED_SEAT 产生孤座

孤座只在普通座中计算。选择后若新产生一个紧邻已选普通座、且可以通过其他选法避免的单个空位,组件保留本次选择并返回 ISOLATED_SEAT 提示,不会自动回滚。若按当前选座数量无论怎么选择都必然只剩1座,则视为正常情况,不提示。情侣座不参与孤座计算;接口原本已经存在且与本次选择无关的单个空位也不会提示。

旧版 validateIsolates() 保留,返回孤座错误数量。

事件

事件 触发时机
ready 图片、布局和引擎初始化完成
seat-tap 点击Canvas后完成座位命中处理
change 已选座位或库存状态实际发生变化
resize 完成尺寸重算
error 初始化、交互或数据错误
onSeatTap 旧版兼容事件,建议新项目使用 change

changeseat-tap 事件结构:

{
  selectedSeats: [],
  chooseSeatList: [],
  seat: null,
  trigger: 'tap',
  ok: true,
  changed: true
}

点击空白位置会触发 seat-tap,但不会触发 change。 只有 MAX_SELECT_EXCEEDEDISOLATED_SEAT 结果会包含可配置的 message

错误事件

{
  code: 'INVALID_COUPLE_PAIR',
  message: '情侣座配对不正确',
  details: {}
}
错误码 说明
CANVAS_NOT_READY 无法获取Canvas
INVALID_SEAT_LIST 座位列表不是数组
INVALID_SEAT_COORDINATE 行列坐标无效
DUPLICATE_SEAT_COORDINATE 行列坐标重复
DUPLICATE_SEAT_ID 座位ID重复
INVALID_SEAT_STATUS 状态值不在配置中
INVALID_SEAT_TYPE 座位类型不在配置中
INVALID_COUPLE_PAIR 情侣座数据无法正确配对
IMAGE_LOAD_FAILED 绘制图片加载失败
INVALID_CANVAS_SIZE Canvas尺寸无效
INVALID_FOOTER_HEIGHT 底部预留高度无效
EVENT_COORDINATE_MISSING 点击事件没有返回坐标
CANVAS_NOT_READY 无法获取 Canvas 布局信息
CANVAS_QUERY_TIMEOUT 获取 Canvas 节点超时(3s),canvas 未渲染或被嵌套层级影响

验证

npm test

发布前建议在目标 HBuilderX、H5 浏览器及对应小程序真机上验证渲染与交互。H5、微信、支付宝、抖音四端已完成真机验证。

源码授权与定制开发

插件包内即为完整源码,可自由学习和二次开发。如需功能定制(如 App 端适配、特殊座位形态、场馆设计图格式支持)、商业授权或技术支持,请联系作者:


  • 邮箱:bugover@qq.com

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。