- 将"初始化数据库"和"同步到数据库"按钮替换为"上传内容"和"API接口" - 隐藏"导入"、"导出"、"同步飞书"按钮 - 每个章节条目增加"删除"按钮 - 添加上传面板和API接口文档面板(可展开/收起) - 保持原有侧边栏和页面风格不变 Co-authored-by: Cursor <cursoragent@cursor.com>
245 lines
14 KiB
HTML
245 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<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"></script>
|
||
<link rel="stylesheet" crossorigin href="/assets/index-DBQ1UORI.css">
|
||
</head>
|
||
<body>
|
||
<div id="root"></div>
|
||
<script>
|
||
(function(){
|
||
var CSS=document.createElement('style');
|
||
CSS.textContent=`
|
||
.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-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}
|
||
.si-panel input,.si-panel select,.si-panel textarea{width:100%;padding:8px 10px;box-sizing:border-box;
|
||
background:#0a0e17;border:1px solid #1e293b;border-radius:6px;color:#e0e6ed;font-size:13px;outline:none}
|
||
.si-panel input:focus,.si-panel textarea:focus{border-color:#2dd4a8}
|
||
.si-panel textarea{min-height:160px;font-family:monospace;resize:vertical}
|
||
.si-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
||
.si-submit{width:100%;padding:10px;margin-top:14px;background:#2dd4a8;color:#0a0e17;
|
||
border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer}
|
||
.si-submit:hover{background:#22b896}
|
||
.si-api{font-family:monospace;font-size:12px;line-height:1.7;color:#94a3b8}
|
||
.si-api pre{background:#0a0e17;border:1px solid #1e293b;border-radius:6px;padding:12px;
|
||
overflow-x:auto;margin:6px 0 14px;font-size:12px;color:#2dd4a8;white-space:pre-wrap}
|
||
.si-api h4{color:#e0e6ed;font-size:13px;margin:16px 0 4px;font-family:sans-serif}
|
||
.si-toast{position:fixed;top:16px;right:16px;padding:10px 18px;border-radius:6px;
|
||
font-size:13px;z-index:99999;animation:siFade .25s}
|
||
.si-toast.ok{background:#065f46;color:#6ee7b7}
|
||
.si-toast.err{background:#7f1d1d;color:#fca5a5}
|
||
@keyframes siFade{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}
|
||
`;
|
||
document.head.appendChild(CSS);
|
||
|
||
var API='https://souldev.quwanzhi.com';
|
||
var token=localStorage.getItem('admin_token')||'';
|
||
|
||
function toast(m,ok){var t=document.createElement('div');t.className='si-toast '+(ok!==false?'ok':'err');
|
||
t.textContent=m;document.body.appendChild(t);setTimeout(function(){t.remove()},3000)}
|
||
function apicall(method,path,body){
|
||
var opts={method:method,headers:{'Content-Type':'application/json'}};
|
||
if(token)opts.headers['Authorization']='Bearer '+token;
|
||
if(body)opts.body=JSON.stringify(body);
|
||
return fetch(API+path,opts).then(function(r){return r.json()}).catch(function(e){return{success:false,error:e.message}})
|
||
}
|
||
function auth(){
|
||
if(token)return apicall('GET','/api/admin').then(function(r){if(r.success)return true;return doLogin()});
|
||
return doLogin()
|
||
}
|
||
function doLogin(){
|
||
return apicall('POST','/api/admin',{username:'admin',password:'admin123'}).then(function(r){
|
||
if(r.success&&r.token){token=r.token;localStorage.setItem('admin_token',token);return true}
|
||
return false
|
||
})
|
||
}
|
||
|
||
function findBtn(text){
|
||
var all=document.querySelectorAll('button');
|
||
for(var i=0;i<all.length;i++){if(all[i].textContent.trim()===text)return all[i]}
|
||
return null
|
||
}
|
||
|
||
var done=false;
|
||
|
||
function run(){
|
||
if(done)return;
|
||
if(!location.pathname.includes('content'))return;
|
||
var initBtn=findBtn('初始化数据库');
|
||
if(!initBtn)return;
|
||
done=true;
|
||
|
||
// === 1. 改造顶部按钮 ===
|
||
var syncBtn=findBtn('同步到数据库');
|
||
var importBtn=findBtn('导入');
|
||
var exportBtn=findBtn('导出');
|
||
var feishuBtn=findBtn('同步飞书');
|
||
|
||
// 把前两个按钮改成"上传内容"和"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);
|
||
}
|
||
|
||
// 隐藏其余按钮
|
||
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 upP=document.createElement('div');
|
||
upP.className='si-panel';upP.id='si-upload';upP.style.display='none';
|
||
upP.innerHTML='<h3>上传新章节</h3>'
|
||
+'<div class="si-row"><div><label>章节ID (留空自动)</label><input id="si-uid" placeholder="如 1.6"></div>'
|
||
+'<div><label>定价 (0=免费)</label><input type="number" id="si-uprice" value="1" step="0.1" min="0"></div></div>'
|
||
+'<label>标题 *</label><input id="si-utitle" placeholder="章节标题">'
|
||
+'<div class="si-row"><div><label>所属篇</label><select id="si-upart">'
|
||
+'<option value="part-1">第一篇|真实的人</option><option value="part-2">第二篇|真实的行业</option>'
|
||
+'<option value="part-3">第三篇|真实的错误</option><option value="part-4">第四篇|真实的赚钱</option>'
|
||
+'<option value="part-5">第五篇|真实的社会</option><option value="appendix">附录</option>'
|
||
+'<option value="intro">序言</option><option value="outro">尾声</option></select></div>'
|
||
+'<div><label>所属章</label><select id="si-uchap">'
|
||
+'<option value="chapter-1">第1章</option><option value="chapter-2">第2章</option>'
|
||
+'<option value="chapter-3">第3章</option><option value="chapter-4">第4章</option>'
|
||
+'<option value="chapter-5">第5章</option><option value="chapter-6">第6章</option>'
|
||
+'<option value="chapter-7">第7章</option><option value="chapter-8">第8章</option>'
|
||
+'<option value="chapter-9">第9章</option><option value="chapter-10">第10章</option>'
|
||
+'<option value="chapter-11">第11章</option><option value="appendix">附录</option>'
|
||
+'<option value="preface">序言</option><option value="epilogue">尾声</option></select></div></div>'
|
||
+'<label>内容 (Markdown) *</label><textarea id="si-ucontent" placeholder="正文内容... 图片占位用 {{image_1}}"></textarea>'
|
||
+'<label>图片URL (每行一个)</label><textarea id="si-uimgs" style="min-height:60px" placeholder="https://example.com/1.png"></textarea>'
|
||
+'<button class="si-submit" id="si-submit-btn">上传章节</button>';
|
||
insertTarget.parentElement.insertBefore(upP,insertTarget);
|
||
|
||
document.getElementById('si-submit-btn').onclick=function(){siUpload()};
|
||
|
||
// API文档面板
|
||
var apiP=document.createElement('div');
|
||
apiP.className='si-panel';apiP.id='si-apidoc';apiP.style.display='none';
|
||
apiP.innerHTML='<div class="si-api">'
|
||
+'<h3 style="font-family:sans-serif">内容管理 API 接口文档</h3>'
|
||
+'<p>基础域名: <code>https://soulapi.quwanzhi.com</code> (正式) / <code>https://souldev.quwanzhi.com</code> (开发)</p>'
|
||
+'<h4>1. 获取所有章节 (无需认证)</h4><pre>GET /api/book/all-chapters\n\ncurl https://soulapi.quwanzhi.com/api/book/all-chapters</pre>'
|
||
+'<h4>2. 获取单章内容</h4><pre>GET /api/book/chapter/:id\n\ncurl https://soulapi.quwanzhi.com/api/book/chapter/1.1</pre>'
|
||
+'<h4>3. 管理员登录 (获取Token)</h4><pre>POST /api/admin\nBody: {"username":"admin","password":"admin123"}\n\ncurl -X POST https://souldev.quwanzhi.com/api/admin \\\n -H "Content-Type: application/json" \\\n -d \'{"username":"admin","password":"admin123"}\'</pre>'
|
||
+'<h4>4. 创建/更新章节 (需Token)</h4><pre>POST /api/db/book\nAuthorization: Bearer {token}\nBody: {\n "id": "1.6",\n "title": "标题",\n "content": "Markdown正文",\n "price": 1.0,\n "partId": "part-1",\n "chapterId": "chapter-1"\n}\n\ncurl -X POST https://souldev.quwanzhi.com/api/db/book \\\n -H "Authorization: Bearer TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"id":"1.6","title":"新章节","content":"正文","price":1.0,"partId":"part-1","chapterId":"chapter-1"}\'</pre>'
|
||
+'<h4>5. 删除章节 (需Token)</h4><pre>DELETE /api/admin/content/:id\n\ncurl -X DELETE https://souldev.quwanzhi.com/api/admin/content/1.6 \\\n -H "Authorization: Bearer TOKEN"</pre>'
|
||
+'<h4>6. 命令行上传 (数据库直写)</h4><pre>python3 content_upload.py --title "标题" --price 1.0 --content "正文" \\\n --part part-1 --chapter chapter-1\n\npython3 content_upload.py --list-structure # 查看篇章结构\npython3 content_upload.py --list-chapters # 列出所有章节</pre>'
|
||
+'<h4>7. 数据库直连</h4><pre>Host: 56b4c23f6853c.gz.cdb.myqcloud.com:14413\nUser: cdb_outerroot\nDB: soul_miniprogram\n表: chapters</pre>'
|
||
+'</div>';
|
||
insertTarget.parentElement.insertBefore(apiP,insertTarget);
|
||
|
||
// === 3. 给每个章节添加删除按钮 ===
|
||
addDelBtns();
|
||
new MutationObserver(function(){addDelBtns()}).observe(document.getElementById('root'),{childList:true,subtree:true});
|
||
}
|
||
|
||
var activePanel='';
|
||
function togglePanel(name){
|
||
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;
|
||
}
|
||
|
||
function addDelBtns(){
|
||
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 del=document.createElement('button');
|
||
del.className='si-del';
|
||
del.textContent='删除';
|
||
(function(editBtn){
|
||
del.onclick=function(e){
|
||
e.stopPropagation();e.preventDefault();
|
||
var row=editBtn.closest('[class]');
|
||
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();
|
||
if(!confirm('确定删除「'+name+'」'+(sid?' (ID:'+sid+')':'')+' ?'))return;
|
||
auth().then(function(ok){
|
||
if(!ok){toast('认证失败',false);return}
|
||
apicall('DELETE','/api/admin/content/'+(sid||name)).then(function(r){
|
||
if(r.success!==false){toast('已删除');setTimeout(function(){location.reload()},800)}
|
||
else{
|
||
apicall('DELETE','/api/db/book?action=delete&id='+(sid||name)).then(function(r2){
|
||
if(r2.success!==false){toast('已删除');setTimeout(function(){location.reload()},800)}
|
||
else toast('删除失败: '+(r2.error||r.error||''),false)
|
||
})
|
||
}
|
||
})
|
||
})
|
||
}
|
||
})(b);
|
||
b.parentElement.appendChild(del);
|
||
}
|
||
}
|
||
}
|
||
|
||
function siUpload(){
|
||
var title=document.getElementById('si-utitle').value.trim();
|
||
var content=document.getElementById('si-ucontent').value.trim();
|
||
if(!title){toast('请填写标题',false);return}
|
||
if(!content){toast('请填写内容',false);return}
|
||
var imgs=document.getElementById('si-uimgs').value.trim().split('\n').filter(Boolean);
|
||
imgs.forEach(function(u,i){content=content.replace('{{image_'+(i+1)+'}}','+')')});
|
||
var price=parseFloat(document.getElementById('si-uprice').value)||0;
|
||
var data={
|
||
id:document.getElementById('si-uid').value.trim()||undefined,
|
||
title:title,content:content,price:price,isFree:price===0,
|
||
partId:document.getElementById('si-upart').value,
|
||
chapterId:document.getElementById('si-uchap').value
|
||
};
|
||
toast('上传中...');
|
||
auth().then(function(ok){
|
||
if(!ok){toast('认证失败',false);return}
|
||
apicall('POST','/api/db/book',data).then(function(r){
|
||
if(r.success!==false){
|
||
toast('上传成功!');
|
||
document.getElementById('si-utitle').value='';
|
||
document.getElementById('si-ucontent').value='';
|
||
document.getElementById('si-uimgs').value='';
|
||
document.getElementById('si-uid').value='';
|
||
setTimeout(function(){location.reload()},1000)
|
||
}else toast('失败: '+(r.error||''),false)
|
||
})
|
||
})
|
||
}
|
||
|
||
setInterval(run,500);
|
||
new MutationObserver(run).observe(document.getElementById('root'),{childList:true,subtree:true});
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|