区域状态与样式

预订渲染器为每个区域轮廓跟踪四种状态:高亮、选中、不可用和已过滤。可通过 svgSectionStyles 分别设置样式,并通过 API 切换。SVG 背景、编辑器创建的形状、自动轮廓和回退轮廓均支持相同行为。

区域状态

每个轮廓可同时启用零个或多个状态:

状态 触发方式 典型用途
highlighted 鼠标悬停或代码设置 悬停反馈
selected 用户点击通用入场区 区域加入购物车
unavailable disableSections() 售罄、受限或关闭的区域
filtered filterSections() 仅显示指定区域子集,如某票价档位

状态保存在 SVG 轮廓元素的 data-* 属性中:data-highlighted、data-selected、data-unavailable、data-filtered。样式由主题配置生成的 CSS 控制。

使用 svgSectionStyles 设置样式

创建渲染器时,在 theme 中传入 svgSectionStyles。每个键对应一个状态,值控制该状态的视觉属性。

import { SeatmapBookingRenderer } from '@seatmap.pro/renderer';

const renderer = new SeatmapBookingRenderer(document.getElementById('seatmap'), {
  publicKey: 'your-public-key',
  theme: {
    svgSectionStyles: {
      default: {
        bgColor: 'rgba(200, 200, 200, 0.2)',
        stroke: { color: '#888', width: '2', opacity: 1 },
        cursor: 'pointer',
      },
      hovered: {
        bgColor: 'rgba(33, 150, 243, 0.5)',
        stroke: { color: '#1565C0', width: '3', opacity: 1 },
        cursor: 'pointer',
      },
      selected: {
        bgColor: 'rgba(76, 175, 80, 0.6)',
        stroke: { color: '#2E7D32', width: '3', opacity: 1 },
        cursor: 'pointer',
      },
      unavailable: {
        bgColor: 'rgba(244, 67, 54, 0.4)',
        stroke: { color: '#C62828', width: '3', opacity: 1 },
        cursor: 'not-allowed',
        opacity: 0.5,
      },
      filtered: {
        bgColor: 'rgba(0, 0, 0, 0.15)',
        stroke: { color: '#999', width: '1', opacity: 0.5 },
        cursor: 'default',
        opacity: 0.3,
      },
    },
  },
});
renderer.loadEvent('your-event-id');

样式属性参考

属性 类型 说明
bgColor string 轮廓填充色。使用 rgba() 可在场地背景上半透明叠加。
stroke.color string 轮廓边框颜色。
stroke.width string 边框宽度,如 '2'。
stroke.opacity number 边框不透明度,0–1。
sectionName.color string 轮廓内区域名称文字颜色。
cursor string 悬停时 CSS 光标,如 'pointer'、'not-allowed'。
opacity number 轮廓整体不透明度,0–1。不可用默认 0.5,已过滤默认 0.3;主题设置优先。

default 状态接受以上全部属性。其他状态(hovered、selected、unavailable、filtered)还支持通过 bgColor 设置填充覆盖层。

OutlineStates 接口

渲染器使用 OutlineStates 跟踪状态。各字段均为可选布尔值,仅设置需要更改的字段。

interface OutlineStates {
  highlighted?: boolean; // Mouse hover or programmatic highlight
  selected?: boolean; // Section added to cart (GA sections)
  unavailable?: boolean; // Disabled -- cannot interact
  filtered?: boolean; // Dimmed by active filter
}

读取状态时,始终返回全部四个字段:

interface IEntityStates {
  highlighted: boolean;
  selected: boolean;
  unavailable: boolean;
  filtered: boolean;
}

API 参考

禁用区域

将区域标记为不可用以阻止交互。禁用区域显示 unavailable 样式,忽略点击。

// Disable specific sections by ID
renderer.disableSections([101, 102, 103]);

// Disable with reset -- re-enable all previously disabled, then disable these
// Useful for category buttons: each click disables a new set without accumulating
renderer.disableSections([201, 202], { resetAll: true });

// Re-enable specific sections
renderer.enableSections([101, 102, 103]);

也可按名称禁用:

renderer.disableSectionsByNames(['Floor A', 'Floor B']);
renderer.enableSectionsByNames(['Floor A']);

过滤区域

仅正常显示指定区域,其余调暗。适用于票价档位、无障碍条件或类别筛选。

// Show only these sections -- all others get the "filtered" style
const premiumIds = [101, 102, 103];
renderer.filterSections(premiumIds);

// Remove all filters -- restore all sections to normal
renderer.removeFilterSections();

过滤逻辑会反向应用传入 ID:数组内的区域保持正常,不在数组中的区域被标记为 filtered。

查询区域

// Get all sections with their current state
const sections = renderer.getSections();
sections.forEach((section) => {
  console.log(section.id, section.name, section.isGa, section.disabled, section.filtered);
});

完整方法列表

方法 说明
disableSections(ids, options?) 按数字 ID 禁用。传入 { resetAll: true },先全部启用再禁用指定项。
enableSections(ids) 重新启用已禁用区域。
disableSectionsByNames(names, options?) 按名称禁用。
enableSectionsByNames(names) 按名称重新启用。
filterSections(ids, options?) 仅正常显示指定区域,其余调暗。
removeFilterSections(ids?) 移除指定区域的过滤状态;省略 ID 则移除全部。
getSections() 返回所有区域及当前状态:id、name、isGa、disabled、filtered。
setOutlineStates(id, states) 底层方法,设置指定状态;传 null 清除,id 为 undefined 时应用到全部。
getOutlineStates(id) 读取区域当前状态,返回全部四个布尔字段。
onSectionStateChange(callback) 注册任意区域状态变化回调,接收区域 ID。

从旧方法名迁移

旧 *SvgSections* 方法已弃用但仍可用,可在合适时机替换:

已弃用 替代方法
disableSvgSectionsByIds() disableSections()
enableSvgSectionsByIds() enableSections()
disableSvgSectionsByNames() disableSectionsByNames()
enableSvgSectionsByNames() enableSectionsByNames()
filterSvgSectionsByIds() filterSections()
removeFilterSvgSectionsByIds() removeFilterSections()

新名称表明这些方法适用于所有区域类型,不仅是绑定 SVG 的区域。

调用已弃用方法时,渲染器会在控制台提示一次推荐替代方法。迁移期间可关闭这些警告:

const renderer = new SeatmapBookingRenderer(element, {
  publicKey: 'your-public-key',
  suppressDeprecationWarnings: true,
});

底层状态 API

自定义界面框架、外部状态同步或同时应用多种状态时,可直接使用底层 API:

// Set multiple states on a single section
renderer.setOutlineStates(101, {
  highlighted: true,
  unavailable: true,
});

// Read current state
const state = renderer.getOutlineStates(101);
// Returns: { highlighted: true, selected: false, unavailable: true, filtered: false }

// Clear all states on a section
renderer.setOutlineStates(101, null);

// Clear all states on all sections (pass undefined as id)
renderer.setOutlineStates(undefined, null);

底层 API 直接设置状态,不重新计算价格,也不更新数据模型。需要同步数据模型的常规流程应使用 disableSections、filterSections 等高层方法。

状态变化回调

注册回调,可让外部侧栏、图例或提示与渲染器状态同步:

renderer.onSectionStateChange((sectionId) => {
  const state = renderer.getOutlineStates(sectionId);
  updateSidebar(sectionId, state);
});

所有状态变化都会触发,包括悬停、选择、禁用、过滤及代码更新。

resetAll 选项

disableSections 和 filterSections 均接受 { resetAll: true },先清除同类全部已有状态,再应用新状态,适合类别筛选按钮等互斥选择:

// Category filter buttons -- each click shows only that category
document.getElementById('btn-vip').addEventListener('click', () => {
  renderer.filterSections(vipSectionIds, { resetAll: true });
});

document.getElementById('btn-standard').addEventListener('click', () => {
  renderer.filterSections(standardSectionIds, { resetAll: true });
});

document.getElementById('btn-all').addEventListener('click', () => {
  renderer.removeFilterSections();
});

不使用 resetAll 时,需要每次先调用 removeFilterSections(),可能造成可见闪烁。

区域悬停与选择

通过渲染器设置启用:

const renderer = new SeatmapBookingRenderer(document.getElementById('seatmap'), {
  publicKey: 'your-public-key',
  highlightSectionOnHover: true,
  enableSectionSelection: true,
  onSectionSelect: (section) => {
    console.log('Selected:', section.id, section.name);
    return true; // return true to confirm selection
  },
  onSectionDeselect: (section) => {
    console.log('Deselected:', section.id, section.name);
    return true;
  },
  onSectionMouseEnter: (section) => {
    // Update external UI (tooltip, sidebar, etc.)
  },
  onSectionMouseLeave: () => {
    // Clear external UI
  },
});

highlightSectionOnHover 为 true 时,悬停应用 hovered 样式。enableSectionSelection 为 true 时,点击通用入场区切换 selected 样式,并触发选择或取消选择回调。

状态组合

同一区域可同时处于多个状态,渲染器按 CSS 优先级应用:

组合 视觉结果
selected + 悬停 更粗描边,完全不透明
unavailable + 悬停 不透明度略增,仍显示不可用
selected + unavailable 降低不透明度,不可用样式在视觉上优先

轮廓类型

四种轮廓来源响应相同的状态与样式:

来源 生成方式 属性
SVG 背景 带区域绑定的平面图 SVG data-outline-source="svg"
Shape 编辑器创建的矩形、多边形和路径 data-outline-source="shape"
Auto 编辑器根据座位组生成 data-outline-source="auto"
Fallback 运行时根据座位位置生成 data-outline-source="fallback"

所有禁用、过滤和选择公共 API 对每种来源一致生效。默认样式也一致,可用 svgSectionStyles.bySource 单独设置来源样式。

使用 bySource 按来源设置样式

默认 svgSectionStyles 应用于全部轮廓。若某来源需要不同表现,例如 SVG 区域强烈高亮,而自动座位区域无悬停效果,可添加 bySource。来源条目只覆盖该来源的全局样式,其他来源保持不变。

const renderer = new SeatmapBookingRenderer(container, {
  publicKey: 'your-public-key',
  theme: {
    svgSectionStyles: {
      hovered: {
        stroke: { color: '#2196F3', width: '3px' },
      },
      bySource: {
        fallback: {
          hovered: { stroke: { color: 'transparent', width: '0' } },
        },
        auto: {
          hovered: { stroke: { color: 'transparent', width: '0' } },
        },
      },
    },
  },
});

此例中 svg 区域保留蓝色悬停,系统生成的 fallback 和 auto 座位区域不显示悬停环。每个来源条目接受与顶层相同的 default、hovered、selected、unavailable、filtered 键。

bySource 控制 SVG 轮廓样式。在 WebGL 覆盖模式下,区域悬停环颜色仍取自顶层 hovered.stroke.color。

动画中的轮廓可见性

为提高性能,平移和缩放动画期间默认隐藏 SVG 轮廓层,完成后恢复。若希望动画期间仍显示(会增加性能开销),设置:

const renderer = new SeatmapBookingRenderer(element, {
  publicKey: 'your-public-key',
  showOutlineLayerOnAnimation: true,
});

完整示例

下面创建带状态样式的渲染器,禁用售罄区域,并根据用户操作筛选指定票价档位:

import { SeatmapBookingRenderer } from '@seatmap.pro/renderer';

const renderer = new SeatmapBookingRenderer(document.getElementById('seatmap'), {
  publicKey: 'your-public-key',
  highlightSectionOnHover: true,
  enableSectionSelection: true,
  theme: {
    svgSectionStyles: {
      default: {
        bgColor: 'rgba(100, 100, 100, 0.1)',
        stroke: { color: '#666', width: '1', opacity: 0.8 },
        cursor: 'pointer',
      },
      hovered: {
        bgColor: 'rgba(33, 150, 243, 0.4)',
        stroke: { color: '#1976D2', width: '2', opacity: 1 },
      },
      selected: {
        bgColor: 'rgba(76, 175, 80, 0.5)',
        stroke: { color: '#388E3C', width: '2', opacity: 1 },
      },
      unavailable: {
        bgColor: 'rgba(158, 158, 158, 0.3)',
        stroke: { color: '#9E9E9E', width: '1', opacity: 0.5 },
        cursor: 'not-allowed',
        opacity: 0.4,
      },
      filtered: {
        bgColor: 'rgba(0, 0, 0, 0.08)',
        stroke: { color: '#ccc', width: '1', opacity: 0.3 },
        cursor: 'default',
        opacity: 0.2,
      },
    },
  },
  onSectionSelect: (section) => {
    console.log('Added to cart:', section.name);
    return true;
  },
  onSectionDeselect: (section) => {
    console.log('Removed from cart:', section.name);
    return true;
  },
});

renderer.loadEvent('your-event-id').then(() => {
  // Disable sold-out sections after event loads
  const soldOutIds = [301, 302];
  renderer.disableSections(soldOutIds);
});

// Filter to a price tier when user clicks a button
document.getElementById('filter-premium').addEventListener('click', () => {
  const premiumIds = [101, 102, 103];
  renderer.filterSections(premiumIds);
});

document.getElementById('clear-filter').addEventListener('click', () => {
  renderer.removeFilterSections();
});

Playground

使用交互式 Playground 实时测试所有区域状态。在 /playground/?preset=unifiedSectionStates 加载 unifiedSectionStates 预设,可查看:

  • SVG、shape、fallback 等各类轮廓的样式。
  • 禁用、启用、过滤、取消过滤、选择及重置按钮。
  • 实时显示 data-* 属性、计算样式及光标值的 DOM 检查器。
  • 所有区域事件的控制台日志。

输入功能、设置、端点或 SDK 方法。