dpl/public/admin-custom/plan-device-display.js

70 lines
5.7 KiB
JavaScript
Vendored
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Independent admin extension. No hooks into React or enforcement data.
const host = document.createElement('div');
host.id = 'plan-device-display-tools';
const style = document.createElement('style');
style.textContent = `
#plan-device-display-tools .entry { position:fixed; right:24px; bottom:24px; z-index:40; border:1px solid #cbd5e1; border-radius:8px; padding:9px 14px; color:#0f172a; background:#fff; cursor:pointer; box-shadow:0 2px 8px #0001; }
#plan-device-display-dialog { width:min(680px,calc(100vw - 40px)); max-height:80vh; overflow:auto; border:1px solid #cbd5e1; border-radius:12px; padding:24px; background:#fff; color:#0f172a; }
#plan-device-display-dialog::backdrop {background:#0007}
#plan-device-display-dialog h2 {font-size:18px;font-weight:600;margin:0 0 8px}
#plan-device-display-dialog .hint {font-size:13px;color:#64748b;margin:8px 0 18px}
#plan-device-display-dialog fieldset {border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin:12px 0}
#plan-device-display-dialog legend {padding:0 6px;font-weight:600}
#plan-device-display-dialog label {font-size:13px;display:block;margin-bottom:6px}
#plan-device-display-dialog input {border:1px solid #cbd5e1;border-radius:6px;padding:8px;width:160px;margin-right:12px}
#plan-device-display-dialog button {border:1px solid #cbd5e1;border-radius:6px;padding:7px 12px;cursor:pointer;background:#f8fafc}
#plan-device-display-dialog .message {font-size:13px;min-height:20px;margin-top:6px}
`;
host.append(style);
const entry = document.createElement('button');
entry.className='entry'; entry.type='button'; entry.textContent='展示设备数设置'; host.append(entry);
const dialog = document.createElement('dialog'); dialog.id='plan-device-display-dialog';
const heading=document.createElement('h2'); heading.textContent='套餐展示设备数'; dialog.append(heading);
const hint=document.createElement('p'); hint.className='hint';
hint.textContent='仅用于页面展示。留空沿用实际限制,0 展示“不限制”。保存不会修改实际设备限制。'; dialog.append(hint);
const rows=document.createElement('div'); dialog.append(rows);
const close=document.createElement('button'); close.type='button'; close.textContent='关闭'; close.addEventListener('click',()=>dialog.close()); dialog.append(close);
host.append(dialog); document.body.append(host);
function syncVisibility() {
const allowed=location.hash.split('?')[0]==='#/finance/plan' && document.querySelector('#root h2');
entry.hidden=!allowed;
if (!allowed && dialog.open) dialog.close();
}
window.addEventListener('hashchange',syncVisibility); syncVisibility();
new MutationObserver(syncVisibility).observe(document.getElementById('root'),{childList:true,subtree:true});
async function api(action,payload) {
let stored; try {stored=JSON.parse(localStorage.getItem('XBOARD_ACCESS_TOKEN')||'null');} catch {throw new Error('请重新登录后台');}
if (!stored?.value || (stored.expire!=null && stored.expire<=Date.now())) throw new Error('请重新登录后台');
const base=new URL(window.settings?.base_url||'/',location.origin);
if (base.origin!==location.origin) throw new Error('后台接口必须同源');
const prefix=base.pathname.replace(/\/$/,'')+'/api/v2/'+encodeURIComponent(window.settings.secure_path)+'/plan/';
const response=await fetch(prefix+action,{method:payload?'POST':'GET',credentials:'same-origin',headers:{Authorization:stored.value,'Content-Type':'application/json','Content-Language':'zh-CN'},...(payload?{body:JSON.stringify(payload)}:{})});
const data=await response.json();
if (!response.ok || data.status==='fail' || data.data===false) throw new Error(data.message||'保存失败');
return data.data;
}
entry.addEventListener('click',async()=>{
rows.textContent='正在读取套餐…'; dialog.showModal(); entry.disabled=true;
try {
const plans=await api('fetch'); rows.replaceChildren();
for (const plan of plans) {
const fieldset=document.createElement('fieldset'); const legend=document.createElement('legend'); legend.textContent=plan.name; fieldset.append(legend);
const actual=document.createElement('p'); actual.className='hint'; actual.textContent='实际设备限制:'+(plan.device_limit==null || Number(plan.device_limit)<=0?'不限制':plan.device_limit); fieldset.append(actual);
const label=document.createElement('label'); label.textContent='展示设备数'; label.htmlFor='display-device-'+plan.id; fieldset.append(label);
const input=document.createElement('input'); input.id=label.htmlFor; input.type='number'; input.min='0'; input.max='4294967295'; input.step='1'; input.placeholder='留空沿用实际限制'; input.value=plan.display_device_limit==null?'':String(plan.display_device_limit); fieldset.append(input);
const save=document.createElement('button'); save.type='button'; save.textContent='保存 '+plan.name; fieldset.append(save);
const message=document.createElement('p'); message.className='message'; message.setAttribute('role','status'); fieldset.append(message);
save.addEventListener('click',async()=>{
const value=input.value.trim(); const number=value===''?null:Number(value);
if (number!==null && (!Number.isInteger(number)||number<0||number>4294967295)) {message.textContent='请填写非负整数,或留空';return;}
save.disabled=true; message.textContent='';
try {await api('update',{id:plan.id,display_device_limit:number}); message.textContent='已保存;实际设备限制未修改。';}
catch(error){message.textContent=error.message||'保存失败';}
finally{save.disabled=false;}
});
rows.append(fieldset);
}
} catch(error){rows.textContent=error.message||'读取失败';}
finally{entry.disabled=false;}
});