feat(perform): 演出配置页(模式切换) + 镜头可视框始终可见

P1 反馈两处修改:
- 顶部加「海选审核 / 演出配置」模式切换;演出配置=独立页,左列仅已确认事件,
  选中即内嵌白模预览(弃用原弹窗),为 P2 在此配置演出细节打底
- 修镜头框只在 dialogue 显式带 camera 时才画的 bug:改为镜头可视区域框始终可见,
  显式镜头点优先→跟随说话人→跟玩家→场景中心,框尺寸按世界单位随舞台缩放+焦点十字
- timeline.js 从弹窗固定ID重构为挂载到任意容器 Timeline.show(host,...);
  离线模型测试复跑两样张全过,重构未破坏逻辑
This commit is contained in:
2026-06-13 11:15:20 +08:00
parent dbf857769e
commit 2a3cf2c66b
4 changed files with 157 additions and 107 deletions

View File

@ -9,6 +9,7 @@
status: null,
selectedNode: null,
dirty: false,
mode: "review", // review=海选审核 / perform=演出配置
by: localStorage.getItem("story_by") || "匿名",
};
window.App = App;
@ -80,7 +81,7 @@
App.current = group; App.ir = JSON.parse(JSON.stringify(d.ir));
App.status = d.status; App.selectedNode = null; App.dirty = false;
$("graph-empty").style.display = "none";
["btn-save", "btn-validate", "btn-playtest", "btn-timeline", "btn-confirm", "btn-discard", "btn-addnode", "btn-autolayout", "btn-addsucc"].forEach(b => $(b).disabled = false);
["btn-save", "btn-validate", "btn-playtest", "btn-confirm", "btn-discard", "btn-addnode", "btn-autolayout", "btn-addsucc"].forEach(b => $(b).disabled = false);
renderAll(true);
GraphUI.focusStart(App.ir); // 定位到开头节点
snapReset(); // 初始化撤销栈
@ -194,8 +195,42 @@
// ---------- 试走 ----------
$("btn-playtest").onclick = () => Playtest.open(App.ir, App.dict);
// ---------- 演出预览(白模时间线)----------
$("btn-timeline").onclick = () => Timeline.open(App.ir, App.dict, App.pointsets);
// ---------- 模式切换:海选审核 / 演出配置 ----------
function setMode(m) {
App.mode = m;
$("mode-review").classList.toggle("active", m === "review");
$("mode-perform").classList.toggle("active", m === "perform");
$("wrap").classList.toggle("hidden", m !== "review");
$("perform-wrap").classList.toggle("hidden", m !== "perform");
$("review-toolbar").style.display = m === "review" ? "" : "none";
Timeline.stop();
if (m === "perform") performLoadList();
}
$("mode-review").onclick = () => setMode("review");
$("mode-perform").onclick = () => setMode("perform");
// ---------- 演出配置页:已确认事件列表 + 内嵌白模预览 ----------
let performCurrent = null;
async function performLoadList() {
let list;
try { list = await (await api("/api/events?status=confirmed")).json(); } catch (e) { return; }
const host = $("perform-list"); host.innerHTML = "";
if (!list.length) { host.innerHTML = '<div class="empty" style="padding:14px">还没有已确认的事件。去「海选审核」确认事件后再来配置演出。</div>'; return; }
list.forEach(e => {
const d = document.createElement("div");
d.className = "ev" + (e.group === performCurrent ? " sel" : "");
d.innerHTML = '<div class="t">' + esc(e.title || e.group) + '</div><div class="g">' + esc(e.group) + ' · ' + esc(e.updated_by || "") + '</div>';
d.onclick = () => performSelect(e.group);
host.appendChild(d);
});
}
async function performSelect(group) {
let d;
try { d = await (await api("/api/events/" + encodeURIComponent(group))).json(); } catch (e) { return; }
performCurrent = group;
performLoadList();
Timeline.show($("perform-main"), d.ir, App.dict, App.pointsets);
}
// ---------- 导入 ----------
let importFiles = []; // 当前已选文件