feat(web): 海选按场景分组 + 删场景点位页签 + 演出真实底图 + 破缓存
- 海选审核左侧改两列:场景列(按新字段 ir.scene 手动归类聚合,含全部/未分类) + 该场景事件列 - 删独立「场景/点位」页签(pointview.js 保留未引用) - 演出配置 Timeline 接真实场景俯视底图(setupShot 覆盖投影范围 + drawStage 叠图,复用 /api/pointsets 的 shot) - 事件 meta 加「所属场景」归类输入框(datalist 提示已有场景名) - db: events 加 scene 列 + 旧库 ALTER 迁移;upsert 镜像 ir.scene;list 返回 - app.py: 首页按文件 mtime 给 js/css 注入 ?v= 破浏览器缓存(根治新html配旧缓存js崩溃→弹口令)
This commit is contained in:
@ -9,7 +9,8 @@
|
||||
status: null,
|
||||
selectedNode: null,
|
||||
dirty: false,
|
||||
mode: "review", // review=海选审核 / perform=演出配置 / points=场景点位
|
||||
mode: "review", // review=海选审核 / perform=演出配置
|
||||
sceneCurrent: null, // 海选审核第一列选中的场景:null=全部 / ""=未分类 / 具体场景名
|
||||
by: localStorage.getItem("story_by") || "匿名",
|
||||
};
|
||||
window.App = App;
|
||||
@ -52,11 +53,36 @@
|
||||
App.events = await r.json();
|
||||
renderList();
|
||||
}
|
||||
function renderList() {
|
||||
function renderList() { renderSceneList(); renderEventList(); }
|
||||
|
||||
// 第一列:按 e.scene 聚合成场景,加「全部」「未分类」两个特殊项。
|
||||
function renderSceneList() {
|
||||
const host = $("scene-list"); host.innerHTML = "";
|
||||
const counts = new Map(); let unclassified = 0;
|
||||
App.events.forEach(e => {
|
||||
const sc = e.scene || "";
|
||||
if (!sc) { unclassified++; return; }
|
||||
counts.set(sc, (counts.get(sc) || 0) + 1);
|
||||
});
|
||||
const mk = (key, label, cnt) => {
|
||||
const d = document.createElement("div");
|
||||
d.className = "scene-item" + (App.sceneCurrent === key ? " sel" : "");
|
||||
d.innerHTML = '<span class="snm">' + esc(label) + '</span><span class="scnt">' + cnt + '</span>';
|
||||
d.onclick = () => { App.sceneCurrent = key; renderList(); };
|
||||
host.appendChild(d);
|
||||
};
|
||||
mk(null, "全部", App.events.length);
|
||||
[...counts.keys()].sort().forEach(k => mk(k, k, counts.get(k)));
|
||||
if (unclassified) mk("", "未分类", unclassified);
|
||||
}
|
||||
|
||||
// 第二列:当前场景下、且匹配搜索的事件。
|
||||
function renderEventList() {
|
||||
const q = $("search").value.trim().toLowerCase();
|
||||
const host = $("event-list"); host.innerHTML = "";
|
||||
const badge = { pending: ["b-pending", "待审"], confirmed: ["b-confirmed", "已确认"], discarded: ["b-discarded", "已丢弃"] };
|
||||
App.events
|
||||
.filter(e => App.sceneCurrent === null || (e.scene || "") === App.sceneCurrent)
|
||||
.filter(e => !q || (e.title || "").toLowerCase().includes(q) || (e.group || "").toLowerCase().includes(q))
|
||||
.forEach(e => {
|
||||
const b = badge[e.status] || ["b-pending", e.status];
|
||||
@ -68,10 +94,10 @@
|
||||
d.onclick = () => selectEvent(e.group);
|
||||
host.appendChild(d);
|
||||
});
|
||||
if (!host.children.length) host.innerHTML = '<div class="empty" style="padding:14px">无事件,点「导入 IR」</div>';
|
||||
if (!host.children.length) host.innerHTML = '<div class="empty" style="padding:14px">该场景下无事件</div>';
|
||||
}
|
||||
$("filter-status").onchange = loadList;
|
||||
$("search").oninput = renderList;
|
||||
$("search").oninput = renderEventList;
|
||||
|
||||
// ---------- 选中事件 ----------
|
||||
async function selectEvent(group) {
|
||||
@ -196,52 +222,22 @@
|
||||
// ---------- 试走 ----------
|
||||
$("btn-playtest").onclick = () => Playtest.open(App.ir, App.dict);
|
||||
|
||||
// ---------- 模式切换:海选审核 / 演出配置 / 场景点位 ----------
|
||||
// ---------- 模式切换:海选审核 / 演出配置 ----------
|
||||
function setMode(m) {
|
||||
App.mode = m;
|
||||
$("mode-review").classList.toggle("active", m === "review");
|
||||
$("mode-perform").classList.toggle("active", m === "perform");
|
||||
$("mode-points").classList.toggle("active", m === "points");
|
||||
$("wrap").classList.toggle("hidden", m !== "review");
|
||||
$("perform-wrap").classList.toggle("hidden", m !== "perform");
|
||||
$("points-wrap").classList.toggle("hidden", m !== "points");
|
||||
$("review-toolbar").style.display = m === "review" ? "" : "none";
|
||||
document.body.classList.toggle("perform-mode", m === "perform"); // 切背景色调
|
||||
document.body.classList.toggle("points-mode", m === "points");
|
||||
Timeline.stop();
|
||||
if (m === "points") PointView.clear();
|
||||
if (m === "perform") performLoadList();
|
||||
if (m === "points") pointsLoadList();
|
||||
}
|
||||
$("mode-review").onclick = () => setMode("review");
|
||||
$("mode-perform").onclick = () => setMode("perform");
|
||||
$("mode-points").onclick = () => setMode("points");
|
||||
|
||||
// ---------- 场景点位页:点位集列表 + 只读白模查看器 ----------
|
||||
let pointsCurrent = null;
|
||||
function pointsLoadList() {
|
||||
const names = Object.keys(App.pointsets || {}).sort();
|
||||
const host = $("points-set-list"); host.innerHTML = "";
|
||||
if (!names.length) { host.innerHTML = '<div class="empty" style="padding:14px">未找到任何点位集(StreamingAssets/Story/PointSets/*.points.json)。</div>'; return; }
|
||||
names.forEach(name => {
|
||||
const ps = App.pointsets[name] || {};
|
||||
const cnt = (ps.anchors || ps.points || []).length;
|
||||
const d = document.createElement("div");
|
||||
d.className = "ev" + (name === pointsCurrent ? " sel" : "");
|
||||
d.innerHTML = '<div class="t">' + esc(name) + '</div><div class="g">'
|
||||
+ (ps.mapId ? '地图 ' + esc(ps.mapId) + ' · ' : '') + cnt + ' 个点</div>';
|
||||
d.onclick = () => pointsSelect(name);
|
||||
host.appendChild(d);
|
||||
});
|
||||
}
|
||||
function pointsSelect(name) {
|
||||
pointsCurrent = name;
|
||||
pointsLoadList();
|
||||
$("points-empty").style.display = "none";
|
||||
PointView.show($("points-view"), name, App.pointsets[name] || {});
|
||||
}
|
||||
|
||||
// ---------- 演出配置页:已确认事件列表 + 内嵌白模预览 ----------
|
||||
// ---------- 演出配置页:已确认事件列表 + 内嵌预览 ----------
|
||||
let performCurrent = null;
|
||||
async function performLoadList() {
|
||||
let list;
|
||||
|
||||
Reference in New Issue
Block a user