npm package: @myycfe/viseat
Description: 一个功能强大且灵活的网页选座库，使用 TypeScript 和 Canvas 构建。支持移动端和桌面端，提供流畅的选座体验。
Latest version: 1.0.1

# 选座库

一个功能强大且灵活的网页选座库，使用 TypeScript 和 Canvas 构建。支持移动端和桌面端，提供流畅的选座体验。

## 概述

本库提供了一个完整的网页选座解决方案，具有以下特点：
- 支持区域导航和详细座位视图的切换
- 基于 Canvas 的高性能渲染
- 流畅的触摸和手势交互
- 自适应不同设备像素比（DPR）
- 支持自定义样式和图标

## 特性

- 🎯 交互式选座：支持点击选择座位，实时反馈
- 🔍 智能缩放：支持手势缩放和平移，自动适应视图
- 📱 移动端优化：针对移动设备优化的触摸交互
- 🎨 样式定制：支持自定义座位、区域和图标样式
- 🖼️ 高性能渲染：基于 Canvas 的渲染，支持高 DPR 设备
- 📊 导航辅助：提供缩略图导航和区域快速定位
- 🎭 区域导航：支持在整体区域视图和详细座位视图之间切换
- 🔄 流畅动画：平滑的视图切换和状态更新动画

## 项目结构

```
@myycfe/viseat/
├── lib/                    # 核心库代码
│   ├── common/            # 通用工具和配置
│   │   ├── config.ts      # 全局配置和常量定义
│   │   └── utils.ts       # 工具函数（节流、防抖、动画等）
│   ├── render/            # 渲染组件
│   │   ├── canvas.ts      # Canvas 渲染实现
│   │   ├── position-icon.ts # 位置指示器渲染
│   │   ├── svg.ts         # 区域导航模式 SVG 渲染
│   │   └── thumbnails.ts  # 缩略图实现
│   ├── event-emit.ts      # 事件处理系统
│   ├── vi-seat.ts     # 主库类
│   └── style.less         # 样式定义
├── demo/                  # 示例实现
├── package.json          # 项目依赖和脚本
├── rollup.config.js      # Rollup 打包配置
├── tsconfig.json         # TypeScript 配置
└── vite.config.ts        # Vite 开发服务器配置
```

## 核心组件

### ViSeat 类

主类，负责协调选座功能。它处理：
- 触摸和手势交互（使用 Hammer.js）
- 视图变换和动画
- 座位选择逻辑
- 事件管理和分发
- Canvas 和 SVG 渲染协调

### 渲染组件

1. **Canvas 渲染器**
   - 处理主要座位布局渲染
   - 管理座位选择状态
   - 实现高性能的缩放和平移
   - 支持高 DPR 设备渲染

2. **位置图标渲染器**
   - 显示已选座位的位置指示器
   - 管理图标动画和状态
   - 支持自定义位置图标

3. **SVG 渲染器**
   - 处理区域导航模式渲染
   - 管理基于 SVG 的可视化
   - 支持自定义区域样式

4. **缩略图渲染器**
   - 提供导航概览
   - 显示当前视图位置
   - 支持自定义缩略图样式

## 配置

库可以通过 `Config` 接口进行配置，包括：

```typescript
interface Config {
  pohMode: boolean;           // 是否启用区域导航模式
  limitRadius: number;        // 区域图和座位图切换阙值（座位半径px）
  pohPositionIcon: string;    // 已选座位在区域图位置图标 URL
  onSeatRender: (uuid: string) => {    // 座位渲染回调
    color?: string;           // 座位颜色
    icon?: string;           // 座位图标
  };
  onSvgRender: (uuid: string) => {     // 区域渲染回调
    fill?: string;           // 填充颜色
    stroke?: string;         // 边框颜色
    labelFill?: string;      // 区域名称颜色
  };
  onInitThumbnailsSvgRender: (uuid: string) => {  // 缩略图区域渲染回调（初始化调用一次）
    fill?: string;
    stroke?: string;
    labelFill?: string;
  };
  onInitThumbnailsSeatRender: (uuid: string) => { // 缩略图座位渲染回调（初始化调用一次）
    color?: string;
    icon?: string;
  };
  getSelectSeatUUIDList: () => string[];  // 获取已选座位列表（用于初始化显示pohPositionIcon）
}
```

## 安装
```typescript
npm install @myycfe/viseat
```

## 使用示例

```typescript
import ViSeat from '@myycfe/viseat';

// 座位数据示例
const seatData = {
  canvasList: [{
      key: "2_2", // canvas键值
      seats: [{
          areaUuid: "i489", // 所属区域
          col: "23", // 列号
          csl: ["2_2"], // 座位所在的canvas列表
          orx: 22.46, // 座位在canvas的X坐标（key为2_1的canvas）
          ory: 152.76, // 座位在canvas的Y坐标（key为2_1的canvas）
          row: "1", // 行号
          seatName: null, // 座位名称
          uuid: "i1" // 座位唯一标识
      }, {
          areaUuid: "i489",
          col: "21",
          csl: ["2_2"],
          orx: 45.05,
          ory: 152.76,
          row: "1",
          seatName: null,
          uuid: "i2"
      }, {
          areaUuid: "i489",
          col: "19",
          csl: ["2_2"],
          orx: 67.64,
          ory: 152.76,
          row: "1",
          seatName: null,
          uuid: "i3"
      }, {
          areaUuid: "i489",
          col: "17",
          csl: ["2_2"],
          orx: 90.22,
          ory: 152.76,
          row: "1",
          seatName: null,
          uuid: "i4"
      }, {
          areaUuid: "i489",
          col: "15",
          csl: ["2_2"],
          orx: 112.81,
          ory: 152.76,
          row: "1",
          seatName: null,
          uuid: "i5"
      }, {
          areaUuid: "i489",
          col: "13",
          csl: ["2_2"],
          orx: 135.4,
          ory: 152.76,
          row: "1",
          seatName: null,
          uuid: "i6"
      }, {
          areaUuid: "i489",
          col: "11",
          csl: ["2_2"],
          orx: 157.98,
          ory: 152.76,
          row: "1",
          seatName: null,
          uuid: "i7"
      }, {
          areaUuid: "i489",
          col: "9",
          csl: ["2_2"],
          orx: 180.57,
          ory: 152.76,
          row: "1",
          seatName: null,
          uuid: "i8"
      }]
  }],
  canvasWidth: 200, // Canvas 宽度
  canvasHeight: 200,  // Canvas 高度
  width: 1048.3368, // 座位图宽度
  height: 869.3663, // 座位图高度
  scale: 0.93333334, // 区域图和座位图缩放比例
  seatSize: 7, // 视图最大比例时座位大小
  svg: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1123.2180000000008\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" font-family=\"Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif\" height=\"931.4638750000013\" version=\"1.1\"><desc>Created with Snap</desc><defs/><path fill=\"#ffffff\" d=\"M 517.619 530.56L 169.257 530.56L 169.257 311.88L 517.619 311.88 517.619 530.56ZM 780.543 314.423L 584.749 314.423L 584.749 533.102L 780.543 533.102 780.543 314.423Z\" id=\"i489\" style=\"stroke-width: 2px; pointer-events: auto;\" transform=\"matrix(1,0,0,1,0,0)\" stroke=\"#e0e0e0\"/><text fill=\"#e0e0e0\" x=\"474.90000000000003\" id=\"i489-label\" y=\"422.491\" style=\"text-anchor: middle; dominant-baseline: central; white-space: pre; font-size: 30px; pointer-events: none;\" transform=\"matrix(1,0,0,1,0,0)\">A区</text></svg>" // 分区svg图
};
// 配置示例
const config = {
  pohMode: false,
  limitRadius: 5,
  pohPositionIcon: "https://example.com/position-icon.png",
  onSeatRender: (uuid) => ({ // 绘制座位调用该回调，接受一个参数：座位唯一uuid，返回座位样式对象
    color: "#ff0000",
    icon: "https://example.com/seat-icon.png"
  }),
  onSvgRender: (uuid) => ({ // 绘制区域调用该回调，接受唯一参数：区域唯一uuid，返回区域样式对象
    fill: "#f0f0f0",
    stroke: "#000000",
    labelFill: "#333333"
  }),
  onInitThumbnailsSvgRender: (uuid) => ({
    fill: "#f0f0f0",
    stroke: "#000000",
    labelFill: "#333333"
  }),
  onInitThumbnailsSeatRender: (uuid) => ({
    color: "#ff0000",
    icon: "https://example.com/seat-icon.png"
  }),
  getSelectSeatUUIDList: () => []
};

// 初始化选座器
const seatSelector = new ViSeat(rootElement, seatData, config);

// 监听事件
seatSelector.addEventListener('ON_CLICK_POH', (uuid) => {
  console.log('点击区域:', uuid);
  return true; // 放大进入座位图
});

seatSelector.addEventListener('ON_CLICK_SEAT', (info) => {
  console.log('选择座位:', info);
  return true;  // 视图放大到最大比例且居中点击座位
});
```

## 事件

库提供了两个主要事件：

1. `ON_CLICK_POH`
   - 触发时机：点击区域时
   - 参数：区域 UUID
   - 返回值：boolean

      true  放大视图进入座位图

      false 视图停留在当前状态
   - 用途：处理区域导航

2. `ON_CLICK_SEAT`
   - 触发时机：选择座位时
   - 参数：座位信息对象
     ```typescript
     {
       uuid: string;      // 座位唯一标识
       areaUuid: string;  // 所属区域
       row: number;       // 行号
       col: number;       // 列号
       seatName: string;  // 座位名称
     }
     ```
   - 返回值：boolean

      true   视图放大到最大比例且居中点击座位

      false  视图停留在当前状态
   - 用途：处理座位选择

## 方法

```typescript
seatSelector.reRenderAllView(resetSize = false, reGetBoundingClientRect = false):void
/*
  该方法接受两个参数：
    resetSize 视图缩小到屏幕内 （初始化最小比例）
    reGetBoundingClientRect (根据rootElement重新设置视图容器宽高、定位)
  重新绘制视图（区域、座位），调用该方法后内部会调用onSvgRender和onSeatRender，根据返回的样式对象重新绘制。  
*/

seatSelector.isInPohView():boolean
// 获取当前是否是区域图模式

seatSelector.resetCanvasSize():void
// 视图缩小到屏幕内 （初始化最小比例）

seatSelector.unSelectedSeat(uuid):void
/*
  接受一个参数：座位uuid
  取消该座位选择
*/ 

seatSelector.destroy():void
// 销毁视图容器，取消事件侦听，释放canvas内存占用

seatSelector.centeredBySeatPoint(uuid: string):void
/*
  接受一个参数：座位uuid
  将视图放大到最大比例，在rootElement容器内居中座位。
*/ 

seatSelector.showSeatListByBBoxRect(uuidList: string[]): void
/*
  接受一个参数：座位uuid数组
  该方法会合并数组内座位的包围盒，将包围盒在rootElement容器内完全显示且居中（类似css的background-size: contain）
*/

seatSelector.reRenderSeats(uuidList: string[] | Set<string>)
/*
  接受一个参数：座位uuid数组 | 座位uuid集合
  重新绘制部分座位
*/
```

## 开发

1. 安装依赖：
   ```bash
   npm install
   ```

2. 启动开发服务器：
   ```bash
   npm run dev
   ```

3. 构建库：
   ```bash
   npm run build
   ```

## 性能优化

- 使用 Canvas 进行高性能渲染
- 实现节流和防抖处理
- 支持设备像素比（DPR）自适应
- 优化动画性能
- 智能的视图更新策略

## 许可证

MIT 许可证