@
feat(web): 演出配置 Timeline 加「场景底图」选择器(venue 特写)
- 后端 /api/sceneshots:列 SceneShots 全部俯视底图(venue 特写) name->{url,bounds}
- timeline.js:底图优先级 ir.stage.backdrop(venue) > 点位集默认 shot;
顶栏加底图下拉 renderMapInfo + applyBackdrop(换底+改投影范围+重画+回调)
- app.js:拉 /api/sceneshots;performSelect 传入;saveBackdrop 写 ir.stage.backdrop 并 PUT
- venue 特写与点位集同 map-local → 换底图后锚点自动落对位(无头实拍擂台验证)
- ir.stage.backdrop 是编辑器元数据:validate 不读、compile 不碰
@
This commit is contained in:
@ -305,6 +305,9 @@
|
||||
// ===================== 状态 & 挂载 =====================
|
||||
let S, model, playT = 0, playing = false, rafId = 0, lastTs = 0, stageCv, stageCtx, els = {}, pending = null, selNode = null, PX = 80, fitMode = false, lastDecisionId = null;
|
||||
let shotBg = null, shotReady = false, shotBounds = null; // 真实场景正交俯视底图
|
||||
// 场景底图选择器:SHOTS=所有可选 venue 特写({name:{url,bounds}});curBackdrop=当前选中底图名(""=点位集默认);
|
||||
// onPick=切换底图回调(app.js 写 ir.stage.backdrop 并持久化);baseBounds=无底图时回退的投影范围。
|
||||
let SHOTS = {}, curPsName = "", curPs = {}, curBackdrop = "", onPick = null, baseBounds = null;
|
||||
|
||||
const TEMPLATE =
|
||||
'<div class="tl-stagepanel">' +
|
||||
@ -322,12 +325,21 @@
|
||||
'<div class="tl-resizer" title="拖动调整时间轴高度"></div>' +
|
||||
'<div class="tl-timelinepanel"><div class="tl-tracks"></div></div>';
|
||||
|
||||
function show(host, IR, DICT, POINTSETS, startId) {
|
||||
function show(host, IR, DICT, POINTSETS, opts) {
|
||||
stopPlay();
|
||||
opts = opts || {};
|
||||
const startId = opts.startId;
|
||||
SHOTS = opts.sceneshots || {};
|
||||
onPick = opts.onPickBackdrop || null;
|
||||
const psName = (IR.stage || {}).point_set || IR.id;
|
||||
const ps = (POINTSETS || {})[psName] || {};
|
||||
curPsName = psName; curPs = ps;
|
||||
curBackdrop = (IR.stage || {}).backdrop || "";
|
||||
S = prepare(IR, ps.anchors || []); model = S;
|
||||
setupShot(ps.shot); // 有真实场景俯视底图 → 覆盖投影范围并异步加载,drawStage 当舞台底
|
||||
baseBounds = Object.assign({}, S.bounds); // 无底图时回退用
|
||||
// 底图优先级:事件指定的 venue 特写(ir.stage.backdrop) > 点位集默认底图
|
||||
const shot = (curBackdrop && SHOTS[curBackdrop]) ? SHOTS[curBackdrop] : ps.shot;
|
||||
setupShot(shot); // 有真实场景俯视底图 → 覆盖投影范围并异步加载,drawStage 当舞台底
|
||||
|
||||
host.innerHTML = TEMPLATE;
|
||||
els = {
|
||||
@ -345,9 +357,7 @@
|
||||
timelinepanel: host.querySelector(".tl-timelinepanel"),
|
||||
playhead: null,
|
||||
};
|
||||
els.mapinfo.textContent = "点位集:" + psName + (ps.mapId ? "(地图 " + ps.mapId + ")" : "") +
|
||||
(S.synthetic ? " · ⚠ 未取到真实坐标,按示意布局自动铺开(走位仍可预览)"
|
||||
: (shotBounds ? " · 真实坐标 · 已叠场景俯视底图" : " · 真实坐标"));
|
||||
renderMapInfo();
|
||||
stageCv = els.stage; stageCtx = stageCv.getContext("2d");
|
||||
|
||||
els.play.onclick = () => playing ? stopPlay() : play();
|
||||
@ -422,6 +432,51 @@
|
||||
shotBg.src = shot.url;
|
||||
}
|
||||
|
||||
// 顶部信息栏 + 场景底图选择器。底图列表来自 /api/sceneshots(venue 特写等)。
|
||||
function renderMapInfo() {
|
||||
const mi = els.mapinfo; if (!mi) return;
|
||||
mi.innerHTML = "";
|
||||
const span = document.createElement("span");
|
||||
span.textContent = "点位集:" + curPsName + (curPs.mapId ? "(地图 " + curPs.mapId + ")" : "") +
|
||||
(S.synthetic ? " · ⚠ 未取到真实坐标,按示意布局自动铺开(走位仍可预览)"
|
||||
: (shotBounds ? " · 真实坐标 · 已叠场景底图" : " · 真实坐标"));
|
||||
mi.appendChild(span);
|
||||
const names = Object.keys(SHOTS).sort();
|
||||
if (names.length) {
|
||||
const lab = document.createElement("label");
|
||||
lab.className = "tl-bd-label"; lab.textContent = "场景底图:";
|
||||
const sel = document.createElement("select");
|
||||
sel.className = "tl-bd-select";
|
||||
const def = document.createElement("option");
|
||||
def.value = ""; def.textContent = curPs.shot ? "(点位集默认)" : "(无)";
|
||||
sel.appendChild(def);
|
||||
names.forEach(n => {
|
||||
const o = document.createElement("option");
|
||||
o.value = n; o.textContent = n;
|
||||
if (n === curBackdrop) o.selected = true;
|
||||
sel.appendChild(o);
|
||||
});
|
||||
sel.onchange = () => applyBackdrop(sel.value);
|
||||
mi.appendChild(lab); mi.appendChild(sel);
|
||||
}
|
||||
}
|
||||
|
||||
// 切换 Timeline 底图:换底 + 改投影范围 + 重画 + 回调持久化(写 ir.stage.backdrop)。
|
||||
// 锚点(点位集坐标)不变,因 venue 特写与点位集同为 map-local,事件点会落在特写图正确位置上。
|
||||
function applyBackdrop(name) {
|
||||
curBackdrop = name;
|
||||
const shot = (name && SHOTS[name]) ? SHOTS[name] : curPs.shot;
|
||||
if (shot && Array.isArray(shot.bounds) && shot.bounds.length === 4) {
|
||||
setupShot(shot);
|
||||
} else { // 选「无/默认」且无点位集底图 → 回退基础范围
|
||||
shotBg = null; shotReady = false; shotBounds = null;
|
||||
if (baseBounds) S.bounds = Object.assign({}, baseBounds);
|
||||
}
|
||||
renderMapInfo();
|
||||
renderFrame();
|
||||
if (onPick) onPick(name); // app.js: ir.stage.backdrop = name 并 PUT 持久化
|
||||
}
|
||||
|
||||
function restart() { startFrom(firstNode(S.IR)); }
|
||||
|
||||
function refreshTimeline() { orderRows(S); buildTracks(); }
|
||||
|
||||
Reference in New Issue
Block a user