npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

seat-engine

v0.2.0

Published

高性能在线选座引擎

Downloads

235

Readme

Seat Engine - 在线选座引擎

高性能在线选座引擎,支持十万级座位渲染,采用 Canvas + SVG 混合渲染技术。

特性

  • 高性能渲染:支持十万级座位流畅渲染
  • Canvas + SVG 混合:兼顾性能与交互体验
  • 智能虚拟化:视口裁剪,按需渲染
  • 模块化设计:支持 Git Submodule 和 NPM 包
  • TypeScript:完整的类型支持
  • 轻量级:最小化依赖

安装

npm install seat-engine

如果你使用 pnpmyarn

pnpm add seat-engine
# 或
yarn add seat-engine

安装前说明

seat-engine 依赖以下运行时包,请确保宿主项目已安装:

  • react >= 18.0.0
  • react-dom >= 18.0.0
  • @deck.gl/core >= 9.0.0
  • @deck.gl/layers >= 9.0.0
  • @deck.gl/react >= 9.0.0

推荐直接按下面方式安装:

npm install seat-engine react@^18 react-dom@^18 @deck.gl/core@^9 @deck.gl/layers@^9 @deck.gl/react@^9

如果你使用 pnpm

pnpm add seat-engine react@^18 react-dom@^18 @deck.gl/core@^9 @deck.gl/layers@^9 @deck.gl/react@^9

如果你使用 yarn

yarn add seat-engine react@^18 react-dom@^18 @deck.gl/core@^9 @deck.gl/layers@^9 @deck.gl/react@^9

项目状态

  • 当前以代码开源为主,仓库保持尽量精简

开源相关文档

快速开始

最小接入步骤:

  1. 安装 seat-engine 和对应的 peer dependencies
  2. 在页面中引入样式 import 'seat-engine/styles'
  3. 给组件一个有高度的容器
  4. 通过 venueGeoDataloadVenueGeoData 传入场馆数据

下面是一个最小可运行示例:

import { SeatSelector } from 'seat-engine';
import 'seat-engine/styles';

function App() {
  return (
    <div style={{ width: '100%', height: '600px' }}>
      <SeatSelector
        loadVenueGeoData={() => fetch('/api/venue/venue-001').then(res => res.json())}
        onSeatSelect={seat => console.log('选中座位:', seat)}
        onSelectionChange={seats => console.log('已选座位:', seats)}
      />
    </div>
  );
}

npm 使用建议

  • 推荐在 React 18+ 项目中使用
  • 推荐通过 ESM 方式引入
  • 样式不是自动注入的,请显式引入 seat-engine/styles
  • 场馆数据量较大时,建议通过异步接口按需加载
  • 正式环境建议由服务端提供座位状态和价格,前端只负责展示与交互

模块引入方式

ESM(推荐)

import { SeatSelector } from 'seat-engine';
import 'seat-engine/styles';

CJS

const { SeatSelector } = require('seat-engine');
require('seat-engine/styles');

浏览器直接引用(dist)

构建后提供以下产物:

  • dist/esm<script type="module"> 直接使用
  • dist/umd:传统 <script> 引用

ESM 方式

注意:需要自行引入 reactreact-dom@deck.gl/* 的 CDN 版本。

<link rel="stylesheet" href="dist/esm/styles.css" />
<script type="module">
  import { SeatSelector } from './dist/esm/index.js';
</script>

UMD 方式

注意:需要先加载 reactreact-dom@deck.gl/* 的 UMD 版本。

<link rel="stylesheet" href="dist/umd/styles.css" />
<script src="dist/umd/index.umd.js"></script>
<script>
  const { SeatSelector } = window.SeatEngine;
</script>

依赖说明

seat-engine 依赖以下:

  • react
  • react-dom
  • @deck.gl/core
  • @deck.gl/layers
  • @deck.gl/react

数据接入方式

支持两种方式,二选一:

方式一:直接传入数据

适合数据已经在页面初始化阶段拿到的场景。

import { SeatSelector, type VenueGeoData } from 'seat-engine';
import 'seat-engine/styles';

const venueGeoData: VenueGeoData = {
  sectionsGeoJson: {
    type: 'FeatureCollection',
    features: [],
  },
  seatsGeoJson: {
    type: 'FeatureCollection',
    features: [],
  },
};

export function App() {
  return (
    <div style={{ width: '100%', height: '600px' }}>
      <SeatSelector venueGeoData={venueGeoData} />
    </div>
  );
}

方式二:异步加载数据

适合按场馆 ID 请求接口、切换场次或延迟加载的场景。

import { SeatSelector } from 'seat-engine';
import 'seat-engine/styles';

export function App() {
  return (
    <div style={{ width: '100%', height: '600px' }}>
      <SeatSelector
        loadVenueGeoData={() => fetch('/api/venue/venue-001').then(res => res.json())}
      />
    </div>
  );
}

发布到 npm 后用户最常见的坑

  • 没有给组件容器高度,导致看起来像“没渲染”
  • 忘记引入 seat-engine/styles
  • 没有安装 reactreact-dom@deck.gl/* 这些 peer dependencies
  • 后端返回的数据字段不符合 VenueGeoData 结构
  • 把座位状态、价格逻辑完全放在前端,导致正式环境状态不一致

开发检查

日常提交建议执行:

pnpm lint
pnpm type-check

发布前建议额外执行:

pnpm build

SeatSelector API

基础用法

<SeatSelector
  loadVenueGeoData={() => fetch('/api/venue/venue-001').then(res => res.json())}
  onSeatSelect={seat => console.log('selected', seat)}
/>

必填/数据输入

二选一

  • venueGeoData?: VenueGeoData
    直接传入 GeoJSON 数据(推荐,最灵活)。
  • loadVenueGeoData?: () => Promise<VenueGeoData>
    异步加载数据,返回 VenueGeoData

若两者都未提供,组件会显示错误提示。

Props

| Prop | 类型 | 说明 | 默认值 | | --------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | locale | 'zh-CN' \| 'en-GB' \| 'en-US' | UI 文案语言 | zh-CN | | messages | Partial<I18nStrings> | 覆盖默认文案 | — | | maxSelections | number | 最大可选座位数 | Infinity | | theme | { colors?: { available?; selected?; sold?; unavailable?; locked? } } | 状态颜色覆盖 | — | | seatSize | { width: number; height: number } | 座位默认尺寸(世界坐标单位) | — | | seatIconAtlas | string | 自定义座位图标图集,支持图片 URL 或 SVG data URL | 内置座位图标 | | seatIconMapping | Record<string, IconMappingValue> | 自定义图标映射,key 与 getSeatIcon 返回值对应 | 内置 seat 映射 | | getSeatIcon | (feature: SeatGeoFeature) => string | 根据座位数据返回图标 key | () => 'seat' | | worldScale | number | 世界坐标缩放比例(不影响座位尺寸) | 1 | | viewPadding | number | 自动居中边距(像素) | 20 | | seatPickingRadius | number \| { mouse?: number; touch?: number } | 座位点击容错半径(像素),适合提升移动端可点性 | 桌面 6,触摸设备 24 | | showSectionNavigator | boolean | 是否显示区域导航控件 | true | | sectionFocusSeatPixelSize | number | 点击区域导航后座位至少放大到的像素尺寸 | 28 | | rotationConfig | RotationConfig | 旋转解释(角度语义近似 AutoCAD,Y 轴默认向下) | { unit:'deg', positiveDirection:'ccw', zeroDirection:'+x', offsetDeg:0, yAxisDirection:'down' } | | sectionLabelLayerStyle | SectionLabelLayerStyle | 分区标签图层样式覆盖 | — | | seatLabelLayerStyle | SeatLabelLayerStyle | 座位标签图层样式覆盖 | — | | onSeatSelect | (seat: SeatSelectionInfo) => void | 选中回调 | — | | onSeatDeselect | (seat: SeatSelectionInfo) => void | 取消选中回调 | — | | onSelectionChange | (seats: SeatSelectionInfo[]) => void | 选中集合变化回调 | — | | renderSeatInfo | (props: SeatInfoRenderProps) => ReactNode | 自定义选座信息 UI(替代默认面板) | — | | className | string | 外层容器类名 | — | | style | CSSProperties | 外层容器样式 | — | | disabled | boolean | 禁用选座并隐藏选座信息面板 | false |

自定义座位图标

默认座位图标是一个可着色的矩形 SVG。如果需要区分普通座、VIP 座、无障碍座等,可以通过 seatIconAtlasseatIconMappinggetSeatIcon 自定义图标。

seatIconMapping 的 key 必须和 getSeatIcon 返回值一致;如果 getSeatIcon 没有返回值,组件会默认使用 seat

import { SeatSelector, type SeatGeoFeature } from 'seat-engine';
import 'seat-engine/styles';

const seatIconAtlas = `data:image/svg+xml;utf8,${encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="64" viewBox="0 0 128 64">
  <rect x="0" y="0" width="64" height="64" rx="10" fill="white" />
  <circle cx="96" cy="32" r="28" fill="white" />
</svg>
`)}`;

const seatIconMapping = {
  seat: { x: 0, y: 0, width: 64, height: 64, mask: true, anchorX: 32, anchorY: 32 },
  vip: { x: 64, y: 0, width: 64, height: 64, mask: true, anchorX: 32, anchorY: 32 },
};

export function App() {
  return (
    <div style={{ width: '100%', height: '600px' }}>
      <SeatSelector
        loadVenueGeoData={() => fetch('/api/venue/venue-001').then(res => res.json())}
        seatIconAtlas={seatIconAtlas}
        seatIconMapping={seatIconMapping}
        getSeatIcon={(feature: SeatGeoFeature) =>
          feature.properties.seatType === 'vip' ? 'vip' : 'seat'
        }
      />
    </div>
  );
}

类型定义

VenueGeoData(组件数据输入)

interface VenueGeoData {
  sectionsGeoJson: SectionGeoJson;
  seatsGeoJson: SeatGeoJson;
  metadata?: {
    version?: string;
    createdAt?: string;
    updatedAt?: string;
    generator?: string;
  };
}

SectionGeoJson(区域几何数据)

type SectionGeoJson = FeatureCollection<SectionGeoProperties, Polygon | MultiPolygon>;

interface SectionGeoProperties {
  sectionId: string;
  name: string;
  fillColor?: string;
}

SeatGeoJson(座位几何数据)

type SeatGeoJson = FeatureCollection<SeatGeoProperties, Point>;

interface SeatGeoProperties {
  seatId: string; // 唯一标识
  seatLabel?: string; // 展示用座位号
  sectionId: string;
  status: 'available' | 'sold' | 'unavailable' | 'locked';
  price?: number;
  seatType?: string;
  rotation?: number;
  width?: number;
  height?: number;
  rowLabel?: string;
  colLabel?: string;
  iconKey?: string;
}

SeatSelectionInfo(回调参数)

interface SeatSelectionInfo {
  seatId: string;
  seatLabel?: string;
  sectionId: string;
  sectionName?: string;
  price?: number;
  status?: 'available' | 'sold' | 'unavailable' | 'locked';
  seatType?: string;
  rowLabel?: string;
  colLabel?: string;
  rotation?: number;
}

SeatInfoRenderProps(自定义选座信息 UI 参数)

interface SeatInfoRenderProps {
  selectedSeats: SeatSelectionInfo[];
  maxSelections: number;
  highlightedSeatId?: string | null;
  onSeatItemClick: (seat: SeatSelectionInfo) => void;
  onSeatRemove: (seat: SeatSelectionInfo) => void;
  locale?: 'zh-CN' | 'en-GB' | 'en-US';
  messages?: Partial<I18nStrings>;
}

RotationConfig(旋转解释)

interface RotationConfig {
  unit?: 'deg' | 'rad';
  positiveDirection?: 'cw' | 'ccw';
  zeroDirection?: '+x' | '+y' | '-x' | '-y';
  offsetDeg?: number;
  yAxisDirection?: 'up' | 'down';
}

各参数控制说明:

  • unit: 输入角度的单位。deg 表示输入就是度;rad 会先转成度。
  • positiveDirection: 输入“正角”的方向语义。ccw 表示正角逆时针;cw 表示正角顺时针(内部会取反)。
  • zeroDirection: 输入 对应哪条轴。可选:+x / +y / -x / -y
  • offsetDeg: 在上述换算后再统一加一个偏移角(度),用于整体微调。
  • yAxisDirection: 世界坐标里 y 的方向定义(影响几何坐标,不只是图标角度)。 upy 越大越靠上(数学/CAD 习惯)。 downy 越大越靠下(屏幕坐标习惯)。

默认值:

  • { unit: 'deg', positiveDirection: 'ccw', zeroDirection: '+x', offsetDeg: 0, yAxisDirection: 'down' }
  • 说明:默认 yAxisDirectiondown,这一点与传统 AutoCAD 世界坐标(Y 向上)不同。

SectionLabelLayerStyle(分区标签图层样式)

interface SectionLabelLayerStyle {
  getSize?: number; // 基准字号(zoom=1),放大时会随缩放增大,默认 16
  getColor?: [number, number, number, number]; // 默认 [31, 41, 55, 255]
  fontFamily?: string; // 默认 'Inter, SF Pro Text, PingFang SC, Microsoft YaHei, sans-serif'
  fontWeight?: string | number; // 默认 '400'
  getTextAnchor?: 'start' | 'middle' | 'end'; // 默认 'middle'
  getAlignmentBaseline?: 'top' | 'center' | 'bottom'; // 默认 'center'
}

SeatLabelLayerStyle(座位标签图层样式)

interface SeatLabelLayerStyle {
  getColor?: [number, number, number, number]; // 默认 [51, 51, 51, 255]
  fontFamily?: string; // 默认 'Inter, SF Pro Text, PingFang SC, Microsoft YaHei, sans-serif'
  fontWeight?: string | number; // 默认 '400'
  getTextAnchor?: 'start' | 'middle' | 'end'; // 默认 'middle'
  getAlignmentBaseline?: 'top' | 'center' | 'bottom'; // 默认 'center'
}

GeoJSON 最小结构

sectionsGeoJson

  • FeatureCollection<Polygon|MultiPolygon>
  • properties.sectionId
  • properties.name
  • properties.fillColor?

seatsGeoJson

  • FeatureCollection<Point>
  • properties.seatId(唯一标识)
  • properties.seatLabel?(显示用座位号)
  • properties.sectionId
  • properties.status(available/sold/unavailable/locked)

FeatureCollection 说明

Seat Engine 使用 GeoJSON RFC 7946 中的 FeatureCollection 描述场馆区域与座位数据。

  • FeatureCollectiontype: "FeatureCollection"features 数组组成;每个 Feature 包含 geometry(几何)和 properties(属性)。
  • sectionsGeoJsonFeatureCollection,几何类型为 PolygonMultiPolygon,表示看台/区域轮廓;properties 需包含 sectionIdname,可选 fillColor
  • seatsGeoJsonFeatureCollection,几何类型为 Point,表示座位中心点;properties 需包含 seatIdsectionIdstatus,可选 seatLabelpricerotationwidthheightrowLabelcolLabel 等。

与类型对应关系:SectionGeoJson = FeatureCollection<SectionGeoProperties, Polygon | MultiPolygon>SeatGeoJson = FeatureCollection<SeatGeoProperties, Point>

License

MIT