|
@@ -58,7 +58,10 @@
|
|
|
.task-status{border-radius:20px;padding:2px 9px;font-size:11px;white-space:nowrap;border:1px solid var(--line)}
|
|
.task-status{border-radius:20px;padding:2px 9px;font-size:11px;white-space:nowrap;border:1px solid var(--line)}
|
|
|
.task-status.done{color:#96ffce;background:#10291f}
|
|
.task-status.done{color:#96ffce;background:#10291f}
|
|
|
.task-status.missing{color:#ffd0df;background:#3d1830}
|
|
.task-status.missing{color:#ffd0df;background:#3d1830}
|
|
|
|
|
+ .task-status.invalid{color:#ffd87a;background:#3c2810}
|
|
|
.task-status.running{color:#7ee8ff;background:#112d3a}
|
|
.task-status.running{color:#7ee8ff;background:#112d3a}
|
|
|
|
|
+ .qa-list{background:#241426;border:1px solid #6f3c56;color:#ffd6e6;border-radius:8px;
|
|
|
|
|
+ padding:8px;font-size:11px;line-height:1.45}
|
|
|
.task-prompt{max-height:94px;overflow:auto;background:#160f29;border:1px solid var(--line);
|
|
.task-prompt{max-height:94px;overflow:auto;background:#160f29;border:1px solid var(--line);
|
|
|
border-radius:8px;padding:8px;color:#cfc4ec;font-size:11px;line-height:1.45;white-space:pre-wrap}
|
|
border-radius:8px;padding:8px;color:#cfc4ec;font-size:11px;line-height:1.45;white-space:pre-wrap}
|
|
|
.placeholder{width:82%;height:82%;border:1px dashed #6b5c92;border-radius:12px;display:flex;
|
|
.placeholder{width:82%;height:82%;border:1px dashed #6b5c92;border-radius:12px;display:flex;
|
|
@@ -69,6 +72,9 @@
|
|
|
.stage.clickable{cursor:pointer}
|
|
.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.invalid-preview img{opacity:.46;filter:grayscale(.25) drop-shadow(0 6px 10px rgba(0,0,0,.35))}
|
|
|
|
|
+ .stage-badge{position:absolute;left:10px;top:10px;background:#3c2810;color:#ffd87a;
|
|
|
|
|
+ border:1px solid #80622d;border-radius:999px;padding:4px 9px;font-size:11px;font-weight:700}
|
|
|
.stage canvas{position:absolute;inset:0;width:100%;height:100%}
|
|
.stage canvas{position:absolute;inset:0;width:100%;height:100%}
|
|
|
.name{font-weight:700;font-size:15px}
|
|
.name{font-weight:700;font-size:15px}
|
|
|
.meta{color:var(--muted);font-size:12px;line-height:1.5}
|
|
.meta{color:var(--muted);font-size:12px;line-height:1.5}
|
|
@@ -380,30 +386,36 @@ function renderChars(v){
|
|
|
list.forEach(t=>{
|
|
list.forEach(t=>{
|
|
|
const c=t.asset||{};
|
|
const c=t.asset||{};
|
|
|
const done=t.status==='done' && c.png;
|
|
const done=t.status==='done' && c.png;
|
|
|
|
|
+ const usable=(t.status==='done'||t.status==='invalid') && c.png;
|
|
|
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 displayImg = c.preview || c.png;
|
|
|
const isParts = c.type==='spine_parts' || t.assetType==='spine_parts';
|
|
const isParts = c.type==='spine_parts' || t.assetType==='spine_parts';
|
|
|
const partCount = (c.parts || []).length;
|
|
const partCount = (c.parts || []).length;
|
|
|
- const card=document.createElement('div'); card.className='card '+(done?'':'missing')+(running?' running':'');
|
|
|
|
|
|
|
+ const qaErrors=(t.quality&&t.quality.errors)||[];
|
|
|
|
|
+ const statusClass=running?'running':(t.status==='invalid'?'invalid':(done?'done':'missing'));
|
|
|
|
|
+ const statusText=running?'生成中':(t.status==='invalid'?'需修复':(done?'已生成':'缺失'));
|
|
|
|
|
+ const card=document.createElement('div'); card.className='card '+(usable?'':'missing')+(running?' running':'');
|
|
|
|
|
+ const stageClass = `stage ${usable&&isParts?'clickable':''} ${t.status==='invalid'?'invalid-preview':''}`;
|
|
|
card.innerHTML=`
|
|
card.innerHTML=`
|
|
|
- <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="${stageClass}">${usable?`<img src="${assetUrl(displayImg)}" alt="${esc(t.id)}">${t.status==='invalid'?'<div class="stage-badge">预览未通过</div>':''}`:`<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>
|
|
|
|
|
- ${done&&anims.length?`<div class="row"><select>${anims.map(a=>`<option>${esc(a)}</option>`).join('')}</select></div>`:''}
|
|
|
|
|
|
|
+ <span class="task-status ${statusClass}">${statusText}</span></div>
|
|
|
|
|
+ ${usable&&!isParts&&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> ${isParts?`<span class="pill">拆件 ${partCount}</span> `:''}${esc(t.use)}<br>
|
|
<div class="meta"><span class="pill">${esc(t.assetType||'spine')}</span> ${isParts?`<span class="pill">拆件 ${partCount}</span> `:''}${esc(t.use)}<br>
|
|
|
- 动作: ${esc((done?anims:t.animations||[]).join(', ')||'idle')}</div>
|
|
|
|
|
- ${done&&isParts?'<button class="ghost parts-btn">查看拆件图</button>':''}
|
|
|
|
|
|
|
+ ${isParts?'Cocos 中播放真实骨骼动作;此处只做静态资源检查。':'动作: '+esc((done?anims:t.animations||[]).join(', ')||'idle')}</div>
|
|
|
|
|
+ ${qaErrors.length?`<div class="qa-list">${qaErrors.slice(0,3).map(x=>`<div>需修复:${esc(x)}</div>`).join('')}</div>`:''}
|
|
|
|
|
+ ${usable&&isParts?'<button class="ghost parts-btn">查看拆件图</button>':''}
|
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
|
- <button class="ghost retry-btn" data-kind="characters" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':(done?'重新生成':'补生成 / 重试')}</button>`;
|
|
|
|
|
- if(done&&anims.length){
|
|
|
|
|
|
|
+ <button class="ghost retry-btn" data-kind="characters" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':(usable?'重新生成':'补生成 / 重试')}</button>`;
|
|
|
|
|
+ if(usable&&!isParts&&anims.length){
|
|
|
const img=card.querySelector('img'), selA=card.querySelector('select');
|
|
const img=card.querySelector('img'), selA=card.querySelector('select');
|
|
|
const tgt={el:img, anim:animMap[anims[0]], start:performance.now()};
|
|
const tgt={el:img, anim:animMap[anims[0]], start:performance.now()};
|
|
|
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){
|
|
|
|
|
|
|
+ if(usable&&isParts){
|
|
|
card.querySelector('.stage').onclick=()=>openPartsModal(c,t);
|
|
card.querySelector('.stage').onclick=()=>openPartsModal(c,t);
|
|
|
card.querySelector('.parts-btn').onclick=()=>openPartsModal(c,t);
|
|
card.querySelector('.parts-btn').onclick=()=>openPartsModal(c,t);
|
|
|
}
|
|
}
|