|
@@ -66,6 +66,7 @@
|
|
|
.stage{height:200px;border-radius:10px;background:
|
|
.stage{height:200px;border-radius:10px;background:
|
|
|
repeating-conic-gradient(#241a3d 0 25%, #2c2150 0 50%) 0/22px 22px;
|
|
repeating-conic-gradient(#241a3d 0 25%, #2c2150 0 50%) 0/22px 22px;
|
|
|
display:flex;align-items:flex-end;justify-content:center;overflow:hidden;position:relative}
|
|
display:flex;align-items:flex-end;justify-content:center;overflow:hidden;position:relative}
|
|
|
|
|
+ .stage.clickable{cursor:pointer}
|
|
|
.stage img{max-height:88%;max-width:88%;transform-origin:50% 100%;will-change:transform;
|
|
.stage img{max-height:88%;max-width:88%;transform-origin:50% 100%;will-change:transform;
|
|
|
filter:drop-shadow(0 6px 10px rgba(0,0,0,.35))}
|
|
filter:drop-shadow(0 6px 10px rgba(0,0,0,.35))}
|
|
|
.stage canvas{position:absolute;inset:0;width:100%;height:100%}
|
|
.stage canvas{position:absolute;inset:0;width:100%;height:100%}
|
|
@@ -81,8 +82,20 @@
|
|
|
background:linear-gradient(135deg,var(--accent),var(--accent2));margin:auto;
|
|
background:linear-gradient(135deg,var(--accent),var(--accent2));margin:auto;
|
|
|
display:flex;align-items:center;justify-content:center;font-weight:800;color:#241a3d;font-size:22px}
|
|
display:flex;align-items:center;justify-content:center;font-weight:800;color:#241a3d;font-size:22px}
|
|
|
.empty{color:var(--muted);text-align:center;padding:50px;border:1px dashed var(--line);border-radius:14px}
|
|
.empty{color:var(--muted);text-align:center;padding:50px;border:1px dashed var(--line);border-radius:14px}
|
|
|
|
|
+ .modal{position:fixed;inset:0;background:rgba(8,5,18,.78);display:none;align-items:center;
|
|
|
|
|
+ justify-content:center;padding:28px;z-index:20}
|
|
|
|
|
+ .modal.open{display:flex}
|
|
|
|
|
+ .modal-panel{width:min(980px,96vw);max-height:92vh;overflow:auto;background:var(--panel);
|
|
|
|
|
+ border:1px solid var(--line);border-radius:14px;padding:18px}
|
|
|
|
|
+ .modal-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px}
|
|
|
|
|
+ .modal-head h3{margin:0;font-size:18px}
|
|
|
|
|
+ .parts-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
|
|
|
|
+ .parts-box{background:#160f29;border:1px solid var(--line);border-radius:10px;padding:12px}
|
|
|
|
|
+ .parts-box img{width:100%;max-height:520px;object-fit:contain;
|
|
|
|
|
+ background:repeating-conic-gradient(#241a3d 0 25%, #2c2150 0 50%) 0/22px 22px;border-radius:8px}
|
|
|
code{background:#160f29;padding:1px 6px;border-radius:5px;font-size:12px}
|
|
code{background:#160f29;padding:1px 6px;border-radius:5px;font-size:12px}
|
|
|
a{color:var(--accent2)}
|
|
a{color:var(--accent2)}
|
|
|
|
|
+ @media(max-width:760px){.parts-grid{grid-template-columns:1fr}}
|
|
|
</style>
|
|
</style>
|
|
|
</head>
|
|
</head>
|
|
|
<body>
|
|
<body>
|
|
@@ -230,6 +243,25 @@
|
|
|
<div id="view"></div>
|
|
<div id="view"></div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+<div class="modal" id="partsModal">
|
|
|
|
|
+ <div class="modal-panel">
|
|
|
|
|
+ <div class="modal-head">
|
|
|
|
|
+ <h3 id="partsTitle">拆件预览</h3>
|
|
|
|
|
+ <button class="ghost" id="partsClose">关闭</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="parts-grid">
|
|
|
|
|
+ <div class="parts-box">
|
|
|
|
|
+ <div class="name">完整预览</div>
|
|
|
|
|
+ <img id="partsPreview" alt="完整预览">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="parts-box">
|
|
|
|
|
+ <div class="name">拆件图 / Atlas</div>
|
|
|
|
|
+ <img id="partsAtlas" alt="拆件图">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
<script>
|
|
<script>
|
|
|
const $ = s => document.querySelector(s);
|
|
const $ = s => document.querySelector(s);
|
|
|
let LIB = {characters:[],vfx:[],ui:[]}, ASSET="", ASSET_VER=Date.now(), TAB="chars";
|
|
let LIB = {characters:[],vfx:[],ui:[]}, ASSET="", ASSET_VER=Date.now(), TAB="chars";
|
|
@@ -246,6 +278,13 @@ function missingTasks(){
|
|
|
function markTasksRunning(items, running=true){
|
|
function markTasksRunning(items, running=true){
|
|
|
items.forEach(t=>running?ACTIVE_TASKS.add(taskKey(t.kind,t.id)):ACTIVE_TASKS.delete(taskKey(t.kind,t.id)));
|
|
items.forEach(t=>running?ACTIVE_TASKS.add(taskKey(t.kind,t.id)):ACTIVE_TASKS.delete(taskKey(t.kind,t.id)));
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+function openPartsModal(c, t){
|
|
|
|
|
+ $('#partsTitle').textContent = `${t.chineseName} · 拆件检查`;
|
|
|
|
|
+ $('#partsPreview').src = assetUrl(c.preview || c.png);
|
|
|
|
|
+ $('#partsAtlas').src = assetUrl(c.png);
|
|
|
|
|
+ $('#partsModal').classList.add('open');
|
|
|
|
|
+}
|
|
|
function syncRunningWithLibrary(){
|
|
function syncRunningWithLibrary(){
|
|
|
for(const key of [...ACTIVE_TASKS]){
|
|
for(const key of [...ACTIVE_TASKS]){
|
|
|
const [kind,id] = key.split(':');
|
|
const [kind,id] = key.split(':');
|
|
@@ -331,14 +370,17 @@ function renderChars(v){
|
|
|
const running=ACTIVE_TASKS.has(taskKey('characters',t.id));
|
|
const running=ACTIVE_TASKS.has(taskKey('characters',t.id));
|
|
|
const animMap=c.animations||{};
|
|
const animMap=c.animations||{};
|
|
|
const anims=Object.keys(animMap);
|
|
const anims=Object.keys(animMap);
|
|
|
|
|
+ const displayImg = c.preview || c.png;
|
|
|
|
|
+ const isParts = c.type==='spine_parts' || t.assetType==='spine_parts';
|
|
|
const card=document.createElement('div'); card.className='card '+(done?'':'missing')+(running?' running':'');
|
|
const card=document.createElement('div'); card.className='card '+(done?'':'missing')+(running?' running':'');
|
|
|
card.innerHTML=`
|
|
card.innerHTML=`
|
|
|
- <div class="stage">${done?`<img src="${assetUrl(c.png)}" alt="${esc(t.id)}">`:`<div class="placeholder">${running?'生成中…':'待生成'}<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
|
|
|
|
+ <div class="stage ${done&&isParts?'clickable':''}">${done?`<img src="${assetUrl(displayImg)}" alt="${esc(t.id)}">`:`<div class="placeholder">${running?'生成中…':'待生成'}<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
|
<span class="task-status ${done?'done':(running?'running':'missing')}">${done?'已生成':(running?'生成中':'缺失')}</span></div>
|
|
<span class="task-status ${done?'done':(running?'running':'missing')}">${done?'已生成':(running?'生成中':'缺失')}</span></div>
|
|
|
${done&&anims.length?`<div class="row"><select>${anims.map(a=>`<option>${esc(a)}</option>`).join('')}</select></div>`:''}
|
|
${done&&anims.length?`<div class="row"><select>${anims.map(a=>`<option>${esc(a)}</option>`).join('')}</select></div>`:''}
|
|
|
<div class="meta"><span class="pill">${esc(t.assetType||'spine')}</span> ${esc(t.use)}<br>
|
|
<div class="meta"><span class="pill">${esc(t.assetType||'spine')}</span> ${esc(t.use)}<br>
|
|
|
动作: ${esc((done?anims:t.animations||[]).join(', ')||'idle')}</div>
|
|
动作: ${esc((done?anims:t.animations||[]).join(', ')||'idle')}</div>
|
|
|
|
|
+ ${done&&isParts?'<button class="ghost parts-btn">查看拆件图</button>':''}
|
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
|
${done?'':`<button class="ghost retry-btn" data-kind="characters" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
|
|
${done?'':`<button class="ghost retry-btn" data-kind="characters" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
|
|
|
if(done&&anims.length){
|
|
if(done&&anims.length){
|
|
@@ -347,6 +389,10 @@ function renderChars(v){
|
|
|
animTargets.push(tgt);
|
|
animTargets.push(tgt);
|
|
|
selA.onchange=()=>{ tgt.anim=animMap[selA.value]; tgt.start=performance.now(); };
|
|
selA.onchange=()=>{ tgt.anim=animMap[selA.value]; tgt.start=performance.now(); };
|
|
|
}
|
|
}
|
|
|
|
|
+ if(done&&isParts){
|
|
|
|
|
+ card.querySelector('.stage').onclick=()=>openPartsModal(c,t);
|
|
|
|
|
+ card.querySelector('.parts-btn').onclick=()=>openPartsModal(c,t);
|
|
|
|
|
+ }
|
|
|
grid.appendChild(card);
|
|
grid.appendChild(card);
|
|
|
});
|
|
});
|
|
|
v.appendChild(grid);
|
|
v.appendChild(grid);
|
|
@@ -523,6 +569,8 @@ $('#view').addEventListener('click', async e=>{
|
|
|
$('#retryMissingBtn').disabled=false;
|
|
$('#retryMissingBtn').disabled=false;
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
+$('#partsClose').onclick=()=>$('#partsModal').classList.remove('open');
|
|
|
|
|
+$('#partsModal').onclick=e=>{ if(e.target.id==='partsModal') $('#partsModal').classList.remove('open'); };
|
|
|
$('#gameSel').onchange=e=>loadLibrary(e.target.value);
|
|
$('#gameSel').onchange=e=>loadLibrary(e.target.value);
|
|
|
function currentManifestGame(){
|
|
function currentManifestGame(){
|
|
|
try{ return JSON.parse($('#manifest').value||'{}').game || ''; }catch(e){ return ''; }
|
|
try{ return JSON.parse($('#manifest').value||'{}').game || ''; }catch(e){ return ''; }
|