fix: preserve credit action handlers in user drawer

This commit is contained in:
OpenClaw Agent
2026-09-17 07:04:28 +08:00
parent 215c38f4fb
commit d8cc416b20
+1 -1
View File
@@ -1367,7 +1367,7 @@ async function loadUserCredit(id){try{const c=await api('/admin/users/'+id+'/cre
async function openUserDetail(id){const u=adminUsersCache.find(x=>x.id===id);if(!u)return;const next=u.status==='active'?'disabled':'active';$("detailUserName").textContent=u.username;$("detailUserMeta").textContent="#"+u.id+" · "+u.role+" · "+u.status;$("userDetailContent").innerHTML="<div class='detail-grid'><div class='detail-card'><h4>账户</h4><div class='muted'>角色</div><div>"+esc(u.role)+"</div><div class='muted' style='margin-top:8px'>状态</div><div>"+esc(u.status)+"</div><div class='detail-actions'><button class='btn alt' id='detailReset'>重置密码</button><button class='btn alt' id='detailToggle'>"+(u.status==='active'?'禁用账户':'启用账户')+"</button></div></div><div class='detail-card' id='detailCredits'>加载积分中…</div></div>";const drawer = $('adminUserDetail'); async function openUserDetail(id){const u=adminUsersCache.find(x=>x.id===id);if(!u)return;const next=u.status==='active'?'disabled':'active';$("detailUserName").textContent=u.username;$("detailUserMeta").textContent="#"+u.id+" · "+u.role+" · "+u.status;$("userDetailContent").innerHTML="<div class='detail-grid'><div class='detail-card'><h4>账户</h4><div class='muted'>角色</div><div>"+esc(u.role)+"</div><div class='muted' style='margin-top:8px'>状态</div><div>"+esc(u.status)+"</div><div class='detail-actions'><button class='btn alt' id='detailReset'>重置密码</button><button class='btn alt' id='detailToggle'>"+(u.status==='active'?'禁用账户':'启用账户')+"</button></div></div><div class='detail-card' id='detailCredits'>加载积分中…</div></div>";const drawer = $('adminUserDetail');
const backdrop = $('drawerBackdrop'); const backdrop = $('drawerBackdrop');
if(drawer) drawer.classList.add('active'); if(drawer) drawer.classList.add('active');
if(backdrop) backdrop.classList.add('active');$("detailReset").onclick=()=>resetUserPassword(id);$("detailToggle").onclick=()=>toggleUser(id,next);try{const c=await api('/admin/users/'+id+'/credits');$("detailCredits").innerHTML="<h4>积分</h4><div class='detail-value'>"+c.balance+"</div><div class='muted'>可用 "+c.available+" · 预留 "+c.reserved+" · 下载 "+c.tasks+" 次</div><div class='detail-actions'><button class='btn' id='detailAdd'>增加积分</button><button class='btn alt' id='detailSub'>扣除积分</button></div>";$("detailAdd").onclick=()=>adjustCredits(id,1);$("detailSub").onclick=()=>adjustCredits(id,-1);const l=await api('/admin/users/'+id+'/credits/transactions?limit=30');$("detailCredits").innerHTML+="<div class='ledger' style='margin-top:18px'><div class='muted' style='margin-bottom:6px'>最近积分流水</div>"+((l.data||[]).map(t=>"<div class='ledger-item'><div><b>"+esc(t.type)+"</b> <span class='"+(t.amount>=0?'positive':'negative')+"'>"+(t.amount>=0?'+':'')+t.amount+"</span></div><div class='meta'>余额 "+t.balanceAfter+" · "+esc(t.reason||'')+" · "+esc(t.createdAt||'')+"</div></div>").join('')||"<div class='empty'>暂无流水</div>")+"</div>"}catch(e){$("detailCredits").textContent=e.message}} if(backdrop) backdrop.classList.add('active');$("detailReset").onclick=()=>resetUserPassword(id);$("detailToggle").onclick=()=>toggleUser(id,next);try{const c=await api('/admin/users/'+id+'/credits');$("detailCredits").innerHTML="<h4>积分</h4><div class='detail-value'>"+c.balance+"</div><div class='muted'>可用 "+c.available+" · 预留 "+c.reserved+" · 下载 "+c.tasks+" 次</div><div class='detail-actions'><button class='btn' id='detailAdd'>增加积分</button><button class='btn alt' id='detailSub'>扣除积分</button></div>";$("detailAdd").onclick=()=>adjustCredits(id,1);$("detailSub").onclick=()=>adjustCredits(id,-1);const l=await api('/admin/users/'+id+'/credits/transactions?limit=30');const ledger=document.createElement('div');ledger.className='ledger';ledger.style.marginTop='18px';ledger.innerHTML="<div class='muted' style='margin-bottom:6px'>最近积分流水</div>"+((l.data||[]).map(t=>"<div class='ledger-item'><div><b>"+esc(t.type)+"</b> <span class='"+(t.amount>=0?'positive':'negative')+"'>"+(t.amount>=0?'+':'')+t.amount+"</span></div><div class='meta'>余额 "+t.balanceAfter+" · "+esc(t.reason||'')+" · "+esc(t.createdAt||'')+"</div></div>").join('')||"<div class='empty'>暂无流水</div>");$("detailCredits").appendChild(ledger)}catch(e){$("detailCredits").textContent=e.message}}
async function adjustCredits(id,sign){const raw=prompt(sign>0?'增加多少积分?':'扣除多少积分?','10');if(raw===null)return;const amount=Math.floor(Number(raw));if(!Number.isFinite(amount)||amount<=0){alert('请输入正整数');return}const reason=prompt('调整原因(可选)','管理员调整积分');if(reason===null)return;try{const x=await api('/admin/users/'+id+'/credits',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({amount:sign*amount,reason})});alert('调整成功,当前可用积分:'+x.available);openUserDetail(id);loadAdmin()}catch(e){alert('积分调整失败:'+e.message)}} async function adjustCredits(id,sign){const raw=prompt(sign>0?'增加多少积分?':'扣除多少积分?','10');if(raw===null)return;const amount=Math.floor(Number(raw));if(!Number.isFinite(amount)||amount<=0){alert('请输入正整数');return}const reason=prompt('调整原因(可选)','管理员调整积分');if(reason===null)return;try{const x=await api('/admin/users/'+id+'/credits',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({amount:sign*amount,reason})});alert('调整成功,当前可用积分:'+x.available);openUserDetail(id);loadAdmin()}catch(e){alert('积分调整失败:'+e.message)}}
async function resetUserPassword(id){const pass=prompt('输入新密码(至少 8 位):');if(pass===null)return;if(pass.length<8){alert('密码至少 8 位');return}try{await api('/admin/users/'+id,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pass})});alert('密码已重置,该用户的其他登录会话已失效')}catch(e){alert('密码重置失败:'+e.message)}} async function resetUserPassword(id){const pass=prompt('输入新密码(至少 8 位):');if(pass===null)return;if(pass.length<8){alert('密码至少 8 位');return}try{await api('/admin/users/'+id,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pass})});alert('密码已重置,该用户的其他登录会话已失效')}catch(e){alert('密码重置失败:'+e.message)}}
async function loadCreditLedger(){try{const u=await api('/admin/users');const users=u.data||[];let total=0;for(const x of users)total+=Number(x.balance)||0;const l=await api('/admin/credits/transactions?limit=100');$('adminCreditSummary').innerHTML='<div class="summary-card">用户<strong>'+users.length+'</strong></div><div class="summary-card">当前余额<strong>'+total+'</strong></div><div class="summary-card">流水<strong>'+((l.data||[]).length)+'</strong></div>';$('creditLedger').innerHTML=(l.data||[]).map(t=>'<div class="ledger-item"><div><b>#'+t.userId+'</b> · '+esc(t.type)+' <span class="'+(t.amount>=0?'positive':'negative')+'">'+(t.amount>=0?'+':'')+t.amount+'</span></div><div class="meta">余额 '+t.balanceAfter+' · '+esc(t.reason||'')+' · '+esc(t.createdAt||'')+'</div></div>').join('')||'<div class="empty">暂无积分流水</div>'}catch(e){$('adminCreditSummary').textContent=e.message}} async function loadCreditLedger(){try{const u=await api('/admin/users');const users=u.data||[];let total=0;for(const x of users)total+=Number(x.balance)||0;const l=await api('/admin/credits/transactions?limit=100');$('adminCreditSummary').innerHTML='<div class="summary-card">用户<strong>'+users.length+'</strong></div><div class="summary-card">当前余额<strong>'+total+'</strong></div><div class="summary-card">流水<strong>'+((l.data||[]).length)+'</strong></div>';$('creditLedger').innerHTML=(l.data||[]).map(t=>'<div class="ledger-item"><div><b>#'+t.userId+'</b> · '+esc(t.type)+' <span class="'+(t.amount>=0?'positive':'negative')+'">'+(t.amount>=0?'+':'')+t.amount+'</span></div><div class="meta">余额 '+t.balanceAfter+' · '+esc(t.reason||'')+' · '+esc(t.createdAt||'')+'</div></div>').join('')||'<div class="empty">暂无积分流水</div>'}catch(e){$('adminCreditSummary').textContent=e.message}}