区域状态与样式
预订渲染器为每个区域轮廓跟踪四种状态:高亮、选中、不可用和已过滤。可通过 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 检查器。 - 所有区域事件的控制台日志。