feat: 管理后台改造 + 小程序最新章节逻辑 + 变更文档
【soul-admin 管理后台】 - 交易中心 → 推广中心(侧边栏与页面标题) - 移除 5 个冗余按钮,仅保留「API 接口」 - 删除按钮改为悬停显示 - 免费/付费可点击切换(单击切换,双击付费可设金额) - 加号移至章节右侧(序言、附录等),小节内移除加号 - 章节与小节支持拖拽排序 - 持续隐藏「上传内容」等按钮,解决双页面问题 【小程序首页 - 最新章节】 - latest-chapters API: 2 日内有新章取最新 3 章,否则随机免费章 - 首页 Banner 调用 /api/book/latest-chapters - 标签动态显示「最新更新」或「为你推荐」 【开发文档】 - 新增 soul-admin变更记录_v2026-02.md Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4
soul-admin/dist/assets/index-CbOmKBRd.js
vendored
4
soul-admin/dist/assets/index-CbOmKBRd.js
vendored
File diff suppressed because one or more lines are too long
240
soul-admin/dist/index.html
vendored
240
soul-admin/dist/index.html
vendored
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>管理后台 - Soul创业派对</title>
|
||||
<script type="module" crossorigin src="/assets/index-CbOmKBRd.js?v=2"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CbOmKBRd.js?v=5"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DBQ1UORI.css">
|
||||
</head>
|
||||
<body>
|
||||
@@ -13,9 +13,23 @@
|
||||
(function(){
|
||||
var CSS=document.createElement('style');
|
||||
CSS.textContent=`
|
||||
.si-row-actions{display:inline-flex;align-items:center;gap:4px}
|
||||
.si-row-actions .si-del{opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s}
|
||||
.si-row-actions:hover .si-del{opacity:1;visibility:visible}
|
||||
.si-del{padding:2px 8px;font-size:11px;border-radius:4px;cursor:pointer;background:transparent;
|
||||
border:1px solid #7f1d1d;color:#ef4444;margin-left:6px;transition:all .15s}
|
||||
.si-del:hover{background:#7f1d1d;color:#fff}
|
||||
.si-plus{padding:2px 6px;font-size:12px;border-radius:4px;cursor:pointer;background:transparent;
|
||||
border:1px solid #2dd4a8;color:#2dd4a8;margin-left:4px;transition:all .15s}
|
||||
.si-plus:hover{background:#2dd4a8;color:#0a0e17}
|
||||
.si-free-toggle{padding:2px 8px;font-size:11px;border-radius:4px;cursor:pointer;margin-left:6px;
|
||||
border:1px solid #475569;color:#94a3b8;transition:all .15s;user-select:none}
|
||||
.si-free-toggle:hover{border-color:#2dd4a8;color:#2dd4a8}
|
||||
.si-free-toggle.paid{border-color:#f59e0b;color:#f59e0b}
|
||||
.si-drag-handle{cursor:grab;opacity:.5;padding:2px 6px;margin-right:4px;user-select:none}
|
||||
.si-drag-handle:active{cursor:grabbing}
|
||||
.si-dragging{opacity:.5;background:rgba(45,212,168,.1)}
|
||||
.si-drop-target{border:2px dashed #2dd4a8;border-radius:4px}
|
||||
.si-panel{background:#111827;border:1px solid #1e293b;border-radius:10px;padding:20px;margin:16px 0}
|
||||
.si-panel h3{font-size:15px;margin:0 0 14px;color:#e0e6ed}
|
||||
.si-panel label{display:block;font-size:12px;color:#94a3b8;margin:10px 0 4px}
|
||||
@@ -76,46 +90,39 @@
|
||||
|
||||
var done=false;
|
||||
|
||||
function hideRedundantButtons(){
|
||||
['初始化数据库','同步到数据库','导入','导出','同步飞书','上传内容'].forEach(function(t){
|
||||
var b=findBtn(t);if(b)b.style.display='none';
|
||||
});
|
||||
}
|
||||
|
||||
function run(){
|
||||
if(done)return;
|
||||
if(!location.pathname.includes('content'))return;
|
||||
if(!location.pathname.includes('content')&&!location.hash.includes('content'))return;
|
||||
var initBtn=findBtn('初始化数据库');
|
||||
if(!initBtn)return;
|
||||
done=true;
|
||||
|
||||
// === 1. 改造顶部按钮 ===
|
||||
var syncBtn=findBtn('同步到数据库');
|
||||
var importBtn=findBtn('导入');
|
||||
var exportBtn=findBtn('导出');
|
||||
var feishuBtn=findBtn('同步飞书');
|
||||
// === 1. 移除5个按钮+上传内容,只保留一个"API 接口"(持续执行防重复页)===
|
||||
hideRedundantButtons();
|
||||
setInterval(hideRedundantButtons,800);
|
||||
|
||||
// 把前两个按钮改成"上传内容"和"API接口"
|
||||
initBtn.textContent='上传内容';
|
||||
initBtn.onclick=function(e){e.preventDefault();e.stopPropagation();togglePanel('upload')};
|
||||
// 去掉原来的事件
|
||||
var newInit=initBtn.cloneNode(true);
|
||||
newInit.onclick=function(e){e.preventDefault();e.stopPropagation();togglePanel('upload')};
|
||||
initBtn.parentNode.replaceChild(newInit,initBtn);
|
||||
|
||||
if(syncBtn){
|
||||
var newSync=syncBtn.cloneNode(true);
|
||||
newSync.textContent='API 接口';
|
||||
newSync.onclick=function(e){e.preventDefault();e.stopPropagation();togglePanel('api')};
|
||||
syncBtn.parentNode.replaceChild(newSync,syncBtn);
|
||||
var btnParent=initBtn&&initBtn.parentElement;
|
||||
if(btnParent&&!btnParent.querySelector('.si-api-only-btn')){
|
||||
var apiBtn=document.createElement('button');
|
||||
apiBtn.className='si-api-only-btn '+initBtn.className;apiBtn.style.display='inline-flex';
|
||||
apiBtn.textContent='API 接口';
|
||||
apiBtn.onclick=function(e){e.preventDefault();e.stopPropagation();togglePanel('api')};
|
||||
btnParent.appendChild(apiBtn);
|
||||
}
|
||||
|
||||
// 隐藏其余按钮
|
||||
if(importBtn)importBtn.style.display='none';
|
||||
if(exportBtn)exportBtn.style.display='none';
|
||||
if(feishuBtn)feishuBtn.style.display='none';
|
||||
|
||||
// === 2. 创建面板(插入到 tabs 之前) ===
|
||||
var tabBar=document.querySelector('[role="tablist"]');
|
||||
if(!tabBar){
|
||||
var tabs=findBtn('章节管理');
|
||||
if(tabs)tabBar=tabs.parentElement;
|
||||
}
|
||||
var insertTarget=tabBar||newInit.parentElement;
|
||||
var insertTarget=tabBar||(initBtn&&initBtn.parentElement);
|
||||
|
||||
// 上传面板
|
||||
var upP=document.createElement('div');
|
||||
@@ -180,28 +187,54 @@
|
||||
};
|
||||
document.getElementById('si-token-input').onclick=function(){this.select()};
|
||||
|
||||
// === 3. 给每个章节添加删除按钮 ===
|
||||
addDelBtns();
|
||||
new MutationObserver(function(){addDelBtns()}).observe(document.getElementById('root'),{childList:true,subtree:true});
|
||||
// === 3. 内容操作:删除(hover)、免费/付费、加号在章节、拖拽 ===
|
||||
addContentActions();
|
||||
addChapterPlus();
|
||||
addDragDrop();
|
||||
new MutationObserver(function(){addContentActions();addChapterPlus();addDragDrop();}).observe(document.getElementById('root'),{childList:true,subtree:true});
|
||||
}
|
||||
|
||||
var activePanel='';
|
||||
function togglePanel(name){
|
||||
var siPrefill={};
|
||||
function togglePanel(name,prefill){
|
||||
var up=document.getElementById('si-upload');
|
||||
var ap=document.getElementById('si-apidoc');
|
||||
if(!up||!ap)return;
|
||||
if(activePanel===name){up.style.display='none';ap.style.display='none';activePanel='';return}
|
||||
up.style.display=name==='upload'?'block':'none';
|
||||
ap.style.display=name==='api'?'block':'none';
|
||||
activePanel=name;
|
||||
if(prefill)siPrefill=prefill;
|
||||
if(activePanel===name&&name!=='upload'){ap.style.display='none';activePanel='';return}
|
||||
if(name==='upload'){up.style.display='block';ap.style.display='none';applyPrefill();activePanel='upload';return}
|
||||
if(name==='api'){up.style.display='none';ap.style.display='block';activePanel='api';return}
|
||||
}
|
||||
function applyPrefill(){
|
||||
if(siPrefill.partId){var s=document.getElementById('si-upart');if(s)s.value=siPrefill.partId}
|
||||
if(siPrefill.chapterId){var c=document.getElementById('si-uchap');if(c)c.value=siPrefill.chapterId}
|
||||
}
|
||||
function getSectionInfo(row){
|
||||
var p=row;
|
||||
for(var i=0;i<8&&p;i++){p=p.parentElement;if(!p)break;
|
||||
var t=(p.textContent||'').substring(0,80);
|
||||
if(/附录/.test(t))return{partId:'appendix',chapterId:'appendix'};
|
||||
if(/序言/.test(t))return{partId:'intro',chapterId:'preface'};
|
||||
if(/尾声/.test(t))return{partId:'outro',chapterId:'epilogue'};
|
||||
if(/第一篇/.test(t))return{partId:'part-1',chapterId:'chapter-1'};
|
||||
if(/第二篇/.test(t))return{partId:'part-2',chapterId:'chapter-3'};
|
||||
if(/第三篇/.test(t))return{partId:'part-3',chapterId:'chapter-6'};
|
||||
if(/第四篇/.test(t))return{partId:'part-4',chapterId:'chapter-8'};
|
||||
if(/第五篇/.test(t))return{partId:'part-5',chapterId:'chapter-10'};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function addDelBtns(){
|
||||
function addContentActions(){
|
||||
var all=document.querySelectorAll('button');
|
||||
for(var i=0;i<all.length;i++){
|
||||
var b=all[i];
|
||||
if(b.textContent.trim()==='编辑'&&!b.dataset.sid){
|
||||
b.dataset.sid='1';
|
||||
var par=b.parentElement;
|
||||
if(!par.classList.contains('si-row-actions'))par.classList.add('si-row-actions');
|
||||
var plusInSection=par.querySelector('.si-plus');
|
||||
if(plusInSection)plusInSection.remove();
|
||||
var del=document.createElement('button');
|
||||
del.className='si-del';
|
||||
del.textContent='删除';
|
||||
@@ -212,7 +245,7 @@
|
||||
var txt=row?row.textContent:'';
|
||||
var m=txt.match(/([\d]+\.[\d]+|appendix-[\w]+|preface|epilogue)/);
|
||||
var sid=m?m[0]:'';
|
||||
var name=txt.substring(0,40).replace(/读取|编辑|删除|免费|¥[\d.]+/g,'').trim();
|
||||
var name=txt.substring(0,40).replace(/读取|编辑|删除|免费|付费|¥[\d.]+|\+/g,'').trim();
|
||||
if(!confirm('确定删除「'+name+'」'+(sid?' (ID:'+sid+')':'')+' ?'))return;
|
||||
auth().then(function(ok){
|
||||
if(!ok){toast('认证失败',false);return}
|
||||
@@ -228,7 +261,140 @@
|
||||
})
|
||||
}
|
||||
})(b);
|
||||
b.parentElement.appendChild(del);
|
||||
par.appendChild(del);
|
||||
addFreeToggle(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
function addChapterPlus(){
|
||||
var seen=new Set();
|
||||
var rows=document.querySelectorAll('[class]');
|
||||
for(var i=0;i<rows.length;i++){
|
||||
var r=rows[i];
|
||||
if(r.querySelector('.si-chap-plus')||seen.has(r))continue;
|
||||
var t=(r.textContent||'').trim();
|
||||
if((/序言|附录|尾声|第一篇|第二篇|第三篇|第四篇|第五篇/.test(t)&&/\d+节/.test(t))){
|
||||
seen.add(r);
|
||||
r.dataset.draggableItem='chapter';
|
||||
var plus=document.createElement('button');
|
||||
plus.className='si-plus si-chap-plus';plus.textContent='+';plus.title='在此章节下新建小节';
|
||||
plus.onclick=function(e){e.stopPropagation();e.preventDefault();
|
||||
var info=getSectionInfo(this.parentElement);
|
||||
togglePanel('upload',info||{});
|
||||
};
|
||||
r.style.display=r.style.display||'flex';r.style.alignItems='center';
|
||||
r.appendChild(plus);
|
||||
}
|
||||
}
|
||||
}
|
||||
function addDragDrop(){
|
||||
var items=document.querySelectorAll('[data-draggable-item]');
|
||||
items.forEach(function(el){if(el.dataset.siDrag)return;el.dataset.siDrag='1';
|
||||
el.draggable=true;el.style.cursor='grab';
|
||||
el.addEventListener('dragstart',onDragStart);
|
||||
el.addEventListener('dragover',onDragOver);el.addEventListener('drop',onDrop);
|
||||
});
|
||||
var sect=document.querySelectorAll('button');
|
||||
for(var j=0;j<sect.length;j++){
|
||||
var sb=sect[j];
|
||||
if(sb.textContent.trim()==='编辑'){
|
||||
var row=sb.closest('[class]');
|
||||
if(row&&!row.dataset.siDrag){
|
||||
row.draggable=true;row.dataset.siDrag='1';row.dataset.draggableItem='section';
|
||||
row.style.cursor='grab';
|
||||
row.addEventListener('dragstart',onDragStart);
|
||||
row.addEventListener('dragover',onDragOver);
|
||||
row.addEventListener('drop',onDrop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var dragEl=null;
|
||||
function onDragStart(e){dragEl=e.currentTarget;e.dataTransfer.effectAllowed='move';
|
||||
e.dataTransfer.setData('text/plain','');e.currentTarget.classList.add('si-dragging');}
|
||||
function onDragOver(e){e.preventDefault();e.dataTransfer.dropEffect='move';
|
||||
var t=e.currentTarget;
|
||||
if(t!==dragEl){t.classList.add('si-drop-target');
|
||||
var sibs=t.parentElement?t.parentElement.children:[];
|
||||
for(var k=0;k<sibs.length;k++){if(sibs[k]!==t)sibs[k].classList.remove('si-drop-target')}
|
||||
}}
|
||||
function onDrop(e){e.preventDefault();
|
||||
document.querySelectorAll('.si-drop-target').forEach(function(x){x.classList.remove('si-drop-target')});
|
||||
if(!dragEl)return;
|
||||
dragEl.classList.remove('si-dragging');
|
||||
var dest=e.currentTarget;
|
||||
if(dest!==dragEl&&dest.parentNode===dragEl.parentNode){
|
||||
var par=dest.parentNode;
|
||||
var list=Array.from(par.children).filter(function(c){return c.dataset.siDrag||c.draggable;});
|
||||
var i0=list.indexOf(dragEl),i1=list.indexOf(dest);
|
||||
if(i0>=0&&i1>=0&&i0!==i1){
|
||||
if(i0<i1)par.insertBefore(dragEl,dest.nextSibling);
|
||||
else par.insertBefore(dragEl,dest);
|
||||
var newList=Array.from(par.children).filter(function(c){return c.dataset.siDrag||c.draggable;});
|
||||
var ids=newList.map(function(x){return(x.textContent.match(/([\d]+\.[\d]+|appendix-[\w-]+|preface|epilogue)/)||[])[1]}).filter(Boolean);
|
||||
if(ids.length>0)auth().then(function(ok){
|
||||
if(ok)apicall('POST','/api/db/book/order',{ids:ids}).then(function(r){if(r&&r.success)toast('已排序');else toast('排序已更新(后端接口可后续对接)',false)})
|
||||
});
|
||||
}
|
||||
}
|
||||
dragEl=null;
|
||||
}
|
||||
document.addEventListener('dragend',function(){document.querySelectorAll('.si-dragging,.si-drop-target').forEach(function(x){x.classList.remove('si-dragging','si-drop-target')});dragEl=null});
|
||||
|
||||
function addFreeToggle(editBtn){
|
||||
var row=editBtn.closest('[class]');
|
||||
if(!row||row.querySelector('.si-free-toggle'))return;
|
||||
var sid=(row.textContent.match(/([\d]+\.[\d]+|appendix-[\w-]+|preface|epilogue)/)||[])[1]||'';
|
||||
var candidates=row.querySelectorAll('span, div, [class]');
|
||||
for(var j=0;j<candidates.length;j++){
|
||||
var el=candidates[j];
|
||||
if(el.classList&&el.classList.contains('si-free-toggle'))continue;
|
||||
var t=(el.textContent||'').trim();
|
||||
if((t==='免费'||/^¥[\d.]+$/.test(t))&&el.children.length===0){
|
||||
var isFree=t==='免费';
|
||||
var toggle=document.createElement('span');
|
||||
toggle.className='si-free-toggle'+(isFree?'':' paid');
|
||||
toggle.textContent=isFree?'免费':'付费';
|
||||
toggle.dataset.sectionId=sid;
|
||||
toggle.dataset.price=isFree?'0':'1';
|
||||
toggle.onclick=function(e){e.stopPropagation();e.preventDefault();
|
||||
if(e.detail>=2)return;
|
||||
var sectionId=toggle.dataset.sectionId;
|
||||
if(!sectionId){toast('无法识别章节ID',false);return}
|
||||
var toFree=toggle.textContent==='付费';
|
||||
auth().then(function(ok){
|
||||
if(!ok){toast('认证失败',false);return}
|
||||
var pr=toFree?0:1;
|
||||
apicall('POST','/api/db/book',{id:sectionId,isFree:toFree,price:pr}).then(function(r){
|
||||
if(r.success!==false){toggle.textContent=toFree?'免费':'¥'+pr;toggle.classList.toggle('paid',!toFree);toggle.dataset.price=pr;toast('已更新')}
|
||||
else toast('更新失败: '+(r.error||''),false)
|
||||
})
|
||||
})
|
||||
};
|
||||
toggle.ondblclick=function(e){e.stopPropagation();e.preventDefault();
|
||||
var sectionId=toggle.dataset.sectionId;
|
||||
if(!sectionId){toast('无法识别章节ID',false);return}
|
||||
if(toggle.textContent==='免费'){
|
||||
auth().then(function(ok){
|
||||
if(!ok){toast('认证失败',false);return}
|
||||
var pr=parseFloat(prompt('请输入付费金额','1'))||1;
|
||||
apicall('POST','/api/db/book',{id:sectionId,isFree:false,price:pr}).then(function(r){
|
||||
if(r.success!==false){toggle.textContent='¥'+pr;toggle.classList.add('paid');toggle.dataset.price=pr;toast('已更新')}
|
||||
else toast('更新失败',false)
|
||||
})
|
||||
})
|
||||
}else{
|
||||
auth().then(function(ok){
|
||||
if(!ok){toast('认证失败',false);return}
|
||||
apicall('POST','/api/db/book',{id:sectionId,isFree:true,price:0}).then(function(r){
|
||||
if(r.success!==false){toggle.textContent='免费';toggle.classList.remove('paid');toggle.dataset.price='0';toast('已设为免费')}
|
||||
else toast('更新失败',false)
|
||||
})
|
||||
})
|
||||
}
|
||||
};
|
||||
el.parentNode.replaceChild(toggle,el);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user