trustbase-seller-backend 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/routes/sp-applyment.js +39 -8
- package/test-sp-applyment.js +23 -0
package/package.json
CHANGED
package/routes/sp-applyment.js
CHANGED
|
@@ -480,6 +480,11 @@ input:focus,select:focus{outline:none;border-color:#C4633C;box-shadow:0 0 0 3px
|
|
|
480
480
|
.tag{font-size:11px;border:1px solid #D4CDBF;border-radius:999px;padding:1px 8px;color:#6B655B;margin-left:4px}
|
|
481
481
|
.msg{font-size:13px;margin-top:8px}
|
|
482
482
|
.msg.err{color:#DC2626}.msg.ok{color:#16A34A}
|
|
483
|
+
#toast{position:fixed;top:16px;right:16px;z-index:99;display:flex;flex-direction:column;gap:8px;pointer-events:none}
|
|
484
|
+
.toast{pointer-events:auto;border-radius:10px;padding:12px 18px;font-size:14px;box-shadow:0 4px 16px rgba(51,48,43,.12);animation:tin .18s ease-out}
|
|
485
|
+
.toast.ok{background:#F0FDF4;border:1px solid #BBF7D0;color:#166534}
|
|
486
|
+
.toast.err{background:#FEF2F2;border:1px solid #FCA5A5;color:#991B1B}
|
|
487
|
+
@keyframes tin{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
|
|
483
488
|
@media (max-width:640px){.slots{grid-template-columns:1fr}.app-row{flex-direction:column;align-items:flex-start}}
|
|
484
489
|
</style>
|
|
485
490
|
</head>
|
|
@@ -488,6 +493,8 @@ input:focus,select:focus{outline:none;border-color:#C4633C;box-shadow:0 0 0 3px
|
|
|
488
493
|
<h1>服务商配置</h1>
|
|
489
494
|
<div class="sub">TrustBase 服务商运营台 · 应用/名片/状态 一屏三件事</div>
|
|
490
495
|
|
|
496
|
+
<div id="toast"></div>
|
|
497
|
+
|
|
491
498
|
<div class="chips" id="chips"></div>
|
|
492
499
|
|
|
493
500
|
<div class="box">
|
|
@@ -539,7 +546,21 @@ function api(path,opts){
|
|
|
539
546
|
function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<');}
|
|
540
547
|
function maskMid(m){m=String(m||'');return m.length>16?m.slice(0,12)+'…'+m.slice(-4):m;}
|
|
541
548
|
function maskApp(a){a=String(a||'');return a.length>10?a.slice(0,6)+'****'+a.slice(-4):a;}
|
|
542
|
-
var TYPE_LABEL={app:'APP',mini_program:'小程序',web:'网站'};
|
|
549
|
+
var TYPE_LABEL={app:'APP',mini_program:'小程序',web:'网站'};
|
|
550
|
+
// 操作反馈 toast (2026-10-03 xiaomu 实测: 保存无提示): 右上浮层, 成功绿/失败红, 2.5s 自动消失
|
|
551
|
+
function showToast(ok,text){
|
|
552
|
+
var box=document.getElementById('toast');
|
|
553
|
+
var t=document.createElement('div');
|
|
554
|
+
t.className='toast '+(ok?'ok':'err');
|
|
555
|
+
t.textContent=text;
|
|
556
|
+
box.appendChild(t);
|
|
557
|
+
setTimeout(function(){t.style.opacity='0';t.style.transition='opacity .3s';setTimeout(function(){t.remove();},300);},2500);
|
|
558
|
+
}
|
|
559
|
+
function busy(btn,on,txtBusy){
|
|
560
|
+
if(!btn) return;
|
|
561
|
+
if(on){btn.dataset.txt=btn.textContent;btn.disabled=true;btn.textContent=txtBusy||'保存中…';}
|
|
562
|
+
else{btn.disabled=false;btn.textContent=btn.dataset.txt||btn.textContent;}
|
|
563
|
+
}
|
|
543
564
|
|
|
544
565
|
function render(){
|
|
545
566
|
var c=CFG, comp=c.completeness;
|
|
@@ -612,25 +633,31 @@ function resetForm(){
|
|
|
612
633
|
}
|
|
613
634
|
async function saveApp(){
|
|
614
635
|
var m=document.getElementById('app-msg');
|
|
636
|
+
var btn=document.querySelector('.box .btn[onclick="saveApp()"]');
|
|
615
637
|
var body={name:document.getElementById('f_name').value.trim(),
|
|
616
638
|
appid:document.getElementById('f_appid').value.trim(),
|
|
617
639
|
type:document.getElementById('f_type').value,
|
|
618
640
|
assets:formAssets, is_default:document.getElementById('f_default').checked};
|
|
619
|
-
if(!body.name||!body.appid){m.className='msg err';m.textContent='应用名称和 AppID 必填';return;}
|
|
641
|
+
if(!body.name||!body.appid){showToast(false,'应用名称和 AppID 必填');m.className='msg err';m.textContent='应用名称和 AppID 必填';return;}
|
|
642
|
+
busy(btn,true);
|
|
620
643
|
var r=editingId?await api('/api/sp/apps/'+editingId,{method:'PUT',body:JSON.stringify(body)})
|
|
621
644
|
:await api('/api/sp/apps',{method:'POST',body:JSON.stringify(body)});
|
|
645
|
+
busy(btn,false);
|
|
622
646
|
m.className='msg '+(r.json.ok?'ok':'err');
|
|
623
647
|
m.textContent=r.json.ok?'✓ 已保存':(r.json.message||'保存失败');
|
|
624
|
-
if(r.json.ok){resetForm();load();}
|
|
648
|
+
if(r.json.ok){showToast(true,'✓ 已保存,商户端立即生效');resetForm();load();}
|
|
649
|
+
else showToast(false,r.json.message||'保存失败');
|
|
625
650
|
}
|
|
626
651
|
async function setDefault(id){
|
|
627
652
|
var r=await api('/api/sp/apps/'+id,{method:'PUT',body:JSON.stringify({is_default:true})});
|
|
628
|
-
if(r.json.ok) load();
|
|
653
|
+
if(r.json.ok){showToast(true,'✓ 已设为默认,商户端立即生效');load();}
|
|
654
|
+
else showToast(false,r.json.message||'操作失败');
|
|
629
655
|
}
|
|
630
656
|
async function delApp(id){
|
|
631
657
|
if(!confirm('删除这个应用? (不影响已提交的进件)')) return;
|
|
632
658
|
var r=await api('/api/sp/apps/'+id,{method:'DELETE'});
|
|
633
|
-
if(r.json.ok) load();
|
|
659
|
+
if(r.json.ok){showToast(true,'✓ 已删除,商户端立即生效');load();}
|
|
660
|
+
else showToast(false,r.json.message||'删除失败');
|
|
634
661
|
}
|
|
635
662
|
function shrink(file,cb){
|
|
636
663
|
var rd=new FileReader();
|
|
@@ -655,11 +682,13 @@ async function upFormAsset(ev,kind){
|
|
|
655
682
|
shrink(file,async function(err,b64){
|
|
656
683
|
if(err){m.className='msg err';m.textContent=err.message;return;}
|
|
657
684
|
var r=await api('/api/sp/applyment/upload',{method:'POST',body:JSON.stringify({filename:kind+'.jpg',content_b64:b64})});
|
|
658
|
-
if(r.json.media_id){formAssets[kind]=r.json.media_id;m.textContent='';renderSlots();}
|
|
659
|
-
else{m.className='msg err';m.textContent=r.json.message||'上传失败';}
|
|
685
|
+
if(r.json.media_id){formAssets[kind]=r.json.media_id;m.textContent='';renderSlots();showToast(true,'✓ 素材已上传');}
|
|
686
|
+
else{m.className='msg err';m.textContent=r.json.message||'上传失败';showToast(false,r.json.message||'上传失败');}
|
|
660
687
|
});
|
|
661
688
|
}
|
|
662
689
|
async function saveProfile(){
|
|
690
|
+
var btn=document.querySelector('.btn[onclick="saveProfile()"]');
|
|
691
|
+
busy(btn,true);
|
|
663
692
|
var bp=Number(document.getElementById('p_bp').value);
|
|
664
693
|
var r=await api('/api/sp/config',{method:'PUT',body:JSON.stringify({
|
|
665
694
|
sp_name:document.getElementById('p_name').value.trim(),
|
|
@@ -667,9 +696,11 @@ async function saveProfile(){
|
|
|
667
696
|
sp_official_url:document.getElementById('p_url').value.trim(),
|
|
668
697
|
sp_contact:document.getElementById('p_contact').value.trim(),
|
|
669
698
|
commission_bp:bp})});
|
|
699
|
+
busy(btn,false);
|
|
670
700
|
var m=document.getElementById('p-msg');
|
|
671
701
|
m.className='msg '+(r.json.ok?'ok':'err'); m.textContent=r.json.ok?'✓ 已保存 (商户端 /api/sp/info 立即生效)':(r.json.message||'保存失败');
|
|
672
|
-
if(r.json.ok) load();
|
|
702
|
+
if(r.json.ok){showToast(true,'✓ 已保存,商户端立即生效');load();}
|
|
703
|
+
else showToast(false,r.json.message||'保存失败');
|
|
673
704
|
}
|
|
674
705
|
adminKey(); load();
|
|
675
706
|
</script>
|
package/test-sp-applyment.js
CHANGED
|
@@ -540,3 +540,26 @@ test("多应用: CRUD + 默认唯一 + 旧配置自动迁移 + /info apps 列表
|
|
|
540
540
|
assert.strictEqual(r.status, 400);
|
|
541
541
|
} finally { srv.close(); }
|
|
542
542
|
});
|
|
543
|
+
|
|
544
|
+
test("配置页操作反馈: toast 组件 + 成功/失败文案 + 保存中禁用 (页面断言)", async () => {
|
|
545
|
+
const { srv, base } = await startServer();
|
|
546
|
+
try {
|
|
547
|
+
const r = await fetch(base + "/sp/config", { headers: { Accept: "text/html" } });
|
|
548
|
+
assert.strictEqual(r.status, 200);
|
|
549
|
+
const html = await r.text();
|
|
550
|
+
// toast 组件 (右上浮层, 绿/红, 自动消失)
|
|
551
|
+
assert.ok(html.includes('id="toast"'), "toast 容器");
|
|
552
|
+
assert.ok(html.includes("showToast"), "toast 函数");
|
|
553
|
+
assert.ok(html.includes("position:fixed"), "浮层定位");
|
|
554
|
+
assert.ok(html.includes("2500"), "2.5s 自动消失");
|
|
555
|
+
// 成功/失败文案
|
|
556
|
+
assert.ok(html.includes("✓ 已保存,商户端立即生效"), "成功文案");
|
|
557
|
+
assert.ok(html.includes("r.json.message"), "失败显示后端 message 原文");
|
|
558
|
+
// 保存中禁用
|
|
559
|
+
assert.ok(html.includes("busy(btn,true)") && html.includes("保存中…"), "保存中禁用+文案");
|
|
560
|
+
// 所有 mutating 操作接入: 应用保存/设默认/删除/素材上传/名片保存
|
|
561
|
+
for (const fn of ["saveApp", "setDefault", "delApp", "upFormAsset", "saveProfile"]) {
|
|
562
|
+
assert.ok(html.includes(fn), fn + " 存在");
|
|
563
|
+
}
|
|
564
|
+
} finally { srv.close(); }
|
|
565
|
+
});
|