Files
soul/soul-admin/dist/index.html
卡若 6e276fca61 内容管理页面增强:上传/删除/API文档集成到原有admin页面
- 将"初始化数据库"和"同步到数据库"按钮替换为"上传内容"和"API接口"
- 隐藏"导入"、"导出"、"同步飞书"按钮
- 每个章节条目增加"删除"按钮
- 添加上传面板和API接口文档面板(可展开/收起)
- 保持原有侧边栏和页面风格不变

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:30:20 +08:00

245 lines
14 KiB
HTML
Raw 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.

<!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)+'}}','![图片'+(i+1)+']('+u.trim()+')')});
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>