CoolBotDataSys/frontend/index.html

685 lines
30 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>一尘数据</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body{font-family:-apple-system,BlinkMacSystemFont,PingFang SC,Helvetica,Arial,sans-serif;background:#f2f2f2;font-size:14px;color:#333;min-height:100vh;padding-bottom:60px}
a{color:inherit;text-decoration:none}
/* 顶部 */
.header{background:linear-gradient(135deg,#1a1a2e,#16213e);color:#fff;padding:12px 16px;position:sticky;top:0;z-index:50;display:flex;align-items:center;justify-content:space-between}
.header h1{font-size:17px;font-weight:600}
.hdr-btn{font-size:20px;background:none;border:none;cursor:pointer}
/* 统计 */
.stats{padding:12px;display:grid;grid-template-columns:1fr 1fr;gap:10px}
.stat{background:#fff;border-radius:10px;padding:14px;text-align:center;cursor:pointer}
.stat:active{background:#f0f0f0}
.s-num{font-size:26px;font-weight:700;margin-bottom:4px}
.s-num.blue{color:#1976d2}.s-num.green{color:#2e7d32}.s-num.orange{color:#e65100}.s-num.red{color:#c62828}
.s-label{font-size:12px;color:#999}
.stat.full{grid-column:1/-1;display:flex;justify-content:space-around}
/* 筛选 */
.f-bar{background:#fff;padding:10px 12px;border-bottom:1px solid #eee;overflow-x:auto;white-space:nowrap}
.f-row{display:flex;gap:8px}
.f-btn{flex-shrink:0;padding:5px 14px;border:1px solid #ddd;border-radius:16px;font-size:13px;background:#fff;color:#666;cursor:pointer}
.f-btn:active,.f-btn.on{background:#1a1a2e;color:#fff;border-color:#1a1a2e}
/* 列表 */
.list{padding:10px 12px;padding-bottom:65px}
.post{background:#fff;border-radius:10px;padding:12px;margin-bottom:10px;cursor:pointer}
.post:active{background:#fafafa}
.post-title{font-size:14px;font-weight:500;line-height:1.5;margin-bottom:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.post-meta{display:flex;flex-wrap:wrap;gap:5px;align-items:center;font-size:12px}
.badge{padding:2px 7px;border-radius:4px;font-weight:500}
.b-deal{background:#e8f5e9;color:#2e7d32}.b-want{background:#fff3e0;color:#e65100}.b-normal{background:#e3f2fd;color:#1565c0}.b-lifebuoy{background:#ffebee;color:#c62828}
.price{color:#e53935;font-weight:600}
.author{color:#1976d2}.tm{color:#999}
/* 分页 */
.pager{display:flex;justify-content:center;align-items:center;gap:12px;padding:14px;background:#fff;border-radius:10px;margin-top:10px}
.pg-btn{padding:8px 18px;background:#f5f5f5;border:1px solid #ddd;border-radius:6px;font-size:14px;cursor:pointer}
.pg-btn:disabled{opacity:.4;cursor:not-allowed}
.pg-info{font-size:13px;color:#999}
/* 统计页 */
.stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.st-card{background:#fff;border-radius:10px;padding:16px;text-align:center;cursor:pointer}
.st-card:active{background:#f0f0f0}
.st-num{font-size:32px;font-weight:700;color:#e65100;line-height:1.2}
.st-card.st-deal .st-num{color:#2e7d32}
.st-label{font-size:14px;color:#333;margin:6px 0 4px;font-weight:500}
.st-price{font-size:12px;color:#999}
/* 空/加载 */
.empty,.loading{text-align:center;padding:50px;color:#999}
.empty::before{content:'📭';font-size:40px;display:block;margin-bottom:10px}
/* 底部导航 */
.tab{position:fixed;bottom:0;left:0;right:0;background:#fff;border-top:1px solid #eee;display:flex;z-index:50;padding-bottom:env(safe-area-inset-bottom,0)}
.tab-i{flex:1;text-align:center;padding:10px 0;font-size:11px;color:#999;cursor:pointer}
.tab-i.on{color:#1a1a2e}
.tab-i span{display:block;font-size:22px;margin-bottom:2px}
/* 详情页 */
.detail{position:fixed;inset:0;background:#fff;z-index:100;overflow-y:auto}
.detail-hdr{position:sticky;top:0;background:#fff;padding:12px 16px;border-bottom:1px solid #eee;display:flex;align-items:center;justify-content:space-between;z-index:5}
.back{background:none;border:none;font-size:16px;color:#1976d2;cursor:pointer}
.detail-link{font-size:13px;color:#1976d2}
.detail-body{padding:16px}
.detail-title{font-size:16px;font-weight:600;line-height:1.5;margin-bottom:14px}
.sec{margin-bottom:20px}
.sec-title{font-size:13px;font-weight:600;margin-bottom:10px;padding-bottom:6px;border-bottom:2px solid #1a1a2e}
.info-g{ display:grid;grid-template-columns:1fr 1fr;gap:10px}
.info-i{display:flex;flex-direction:column;gap:2px}
.info-l{font-size:11px;color:#999}
.info-v{font-size:14px;color:#333}
.info-v.prime{font-size:22px;color:#e53935;font-weight:700}
.content{background:#f8f9fa;padding:12px;border-radius:8px;white-space:pre-wrap;font-size:14px;line-height:1.7;word-break:break-all;max-height:300px;overflow-y:auto}
.tag-l{display:flex;flex-wrap:wrap;gap:6px}
.tag{padding:3px 10px;background:#e3f2fd;color:#1565c0;border-radius:12px;font-size:12px}
/* 品类 */
.cat-list{background:#fff;border-radius:10px;padding:12px;margin:0 12px 80px}
.cat-row{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid #f0f0f0;font-size:13px}
.cat-row:last-child{border:none}
</style>
</head>
<body>
<div id="app"></div>
<script>
const API = '/api/v1'
let S = {
v: 'home', // home | list | detail
stats: {},
posts: [],
cur: null,
loading: false,
pg: 1,
ps: 300,
total: 0,
ft: { type: '', cat: '', lifebuoy: '' }
}
function render() {
const app = document.getElementById('app')
if (S.v === 'home') {
const s = S.stats
app.innerHTML = `
<div class="header">
<h1>📊 一尘数据分析</h1>
<button class="hdr-btn" onclick="loadStats()">🔄</button>
</div>
<div class="stats">
<div class="stat" onclick="goList('deal')">
<div class="s-num green">${s.deal_count||0}</div>
<div class="s-label">今日出售</div>
</div>
<div class="stat" onclick="goList('want')">
<div class="s-num orange">${s.want_count||0}</div>
<div class="s-label">今日求购</div>
</div>
<div class="stat" onclick="goList('')">
<div class="s-num blue">${s.today_count||0}</div>
<div class="s-label">今日新增</div>
</div>
<div class="stat" onclick="goList('lifebuoy')">
<div class="s-num red">${s.lifebuoy_count||0}</div>
<div class="s-label">大救生圈</div>
</div>
<div class="stat full">
<div>
<div class="s-num blue" style="font-size:20px">${s.total_posts||0}</div>
<div class="s-label">总帖子</div>
</div>
<div>
<div style="font-size:22px">🟢</div>
<div class="s-label">在线</div>
</div>
</div>
</div>
<div style="padding:0 12px 20px">
<div class="sec-title">📋 品类分布</div>
<div class="cat-list">
<div style="margin-bottom:12px">
<div style="font-size:12px;color:#1976d2;margin-bottom:8px">📅 当日新增</div>
${((S.catReport&&S.catReport.today&&S.catReport.today.categories)||[]).map(c=>`
<div class="cat-row" onclick="goList('','today','${c.category}')">
<span>${c.category}</span>
<span style="color:#1976d2;font-weight:600">${c.count}条</span>
</div>
`).join('')}
</div>
<div>
<div style="font-size:12px;color:#e65100;margin-bottom:8px">⏰ 近1小时新增</div>
${((S.catReport&&S.catReport.last_1h&&S.catReport.last_1h.categories)||[]).map(c=>`
<div class="cat-row" onclick="goList('','1h','${c.category}')">
<span>${c.category}</span>
<span style="color:#e65100;font-weight:600">${c.count}条</span>
</div>
`).join('')}
</div>
</div>
</div>
<div class="tab">
<div class="tab-i on" onclick="S.v='home';render()"><span>📊</span>首页</div>
<div class="tab-i" onclick="goList('')"><span>📋</span>帖子</div>
<div class="tab-i" onclick="goCollections()"><span>💎</span>藏品</div>
<div class="tab-i" onclick="goUsers()"><span>👤</span>用户</div>
<div class="tab-i" onclick="S.v='stats';loadStats()"><span>📈</span>统计</div>
</div>
`
}
if (S.v === 'collections') {
const cols = S.collections || []
app.innerHTML = `
<div class="header"><h1>💎 藏品列表</h1><button class="hdr-btn" onclick="loadCollections()">🔄</button></div>
<div class="filter-bar">
<select id="col-cat" onchange="S.colCat=this.value;render()">
<option value="">全部品类</option>
<option value="龙钞">🐉 龙钞</option>
<option value="马钞">🐴 马钞</option>
<option value="蛇钞">🐍 蛇钞</option>
<option value="其他">📦 其他</option>
</select>
<input type="text" id="col-search" placeholder="搜索名称/冠号" onkeyup="S.colSearch=this.value;render()" style="flex:1;padding:8px;border:1px solid #ddd;border-radius:6px">
</div>
<div class="list">
${cols.filter(c => (!S.colCat || c.category === S.colCat) && (!S.colSearch || (c.name&&c.name.includes(S.colSearch)) || (c.crown_code&&c.crown_code.includes(S.colSearch)))).map(c => `
<div class="post-item" onclick="goCollectionDetail('${c.id}')">
<div class="post-title">${c.name || c.crown_code || '无名'}</div>
<div class="post-meta">
<span class="${c.category==='龙钞'?'green':c.category==='马钞'?'orange':c.category==='蛇钞'?'purple':'gray'}">${c.category||'其他'}</span>
<span>🏷️ ${c.series||'-'}</span>
<span>📅 ${c.issue_year||'-'}</span>
<span>🖥️ ${c.instance_id?c.instance_id.replace('server_','S'):'-'}</span>
</div>
</div>
`).join('')}
${cols.length===0?'<div style="padding:40px;text-align:center;color:#999">暂无藏品,点击刷新</div>':''}
</div>
<div class="tab">
<div class="tab-i" onclick="S.v='home';render()"><span>📊</span>首页</div>
<div class="tab-i" onclick="goList('')"><span>📋</span>帖子</div>
<div class="tab-i on" onclick="goCollections()"><span>💎</span>藏品</div>
<div class="tab-i" onclick="goUsers()"><span>👤</span>用户</div>
<div class="tab-i" onclick="S.v='stats';loadStats()"><span>📈</span>统计</div>
</div>
`
}
if (S.v === 'users') {
const users = S.users || []
app.innerHTML = `
<div class="header"><h1>👤 用户列表</h1><button class="hdr-btn" onclick="loadUsers()">🔄</button></div>
<div class="filter-bar">
<input type="text" id="user-search" placeholder="搜索用户名" onkeyup="S.userSearch=this.value;render()" style="flex:1;padding:8px;border:1px solid #ddd;border-radius:6px">
</div>
<div class="list">
${users.filter(u => !S.userSearch || u.username.includes(S.userSearch)).map(u => `
<div class="post-item">
<div class="post-title">${u.username}</div>
<div class="post-meta">
<span>${u.is_seller?'🏪 商家':'👤 个人'}</span>
<span>📝 ${u.post_count||0}帖</span>
<span>⭐ ${u.credit_score||0}分</span>
<span>📅 ${u.registration_date?u.registration_date.substring(0,10):'-'}</span>
</div>
</div>
`).join('')}
${users.length===0?'<div style="padding:40px;text-align:center;color:#999">暂无用户,点击刷新</div>':''}
</div>
<div class="tab">
<div class="tab-i" onclick="S.v='home';render()"><span>📊</span>首页</div>
<div class="tab-i" onclick="goList('')"><span>📋</span>帖子</div>
<div class="tab-i" onclick="goCollections()"><span>💎</span>藏品</div>
<div class="tab-i on" onclick="goUsers()"><span>👤</span>用户</div>
<div class="tab-i" onclick="S.v='stats';loadStats()"><span>📈</span>统计</div>
</div>
`
}
if (S.v === 'stats') {
const st = S.statsData || {}
const want = st.want || {}
const deal = st.deal || {}
app.innerHTML = `
<div class="header"><h1>📈 统计分析</h1><button class="hdr-btn" onclick="loadStats()">🔄</button></div>
<div style="padding:12px">
<div style="font-size:13px;font-weight:600;color:#e65100;margin-bottom:10px">🔍 求购龙钞类</div>
<div class="stats-grid">
<div class="st-card" onclick="goStatPosts('want_dai4_biao10')">
<div class="st-num">${want.dai4_biao10?.count||0}</div>
<div class="st-label">带4标十</div>
<div class="st-price">均 ${want.dai4_biao10?.avg||0}元</div>
</div>
<div class="st-card" onclick="goStatPosts('want_wu47_biao10')">
<div class="st-num">${want.wu47_biao10?.count||0}</div>
<div class="st-label">无47标十</div>
<div class="st-price">均 ${want.wu47_biao10?.avg||0}元</div>
</div>
<div class="st-card" onclick="goStatPosts('want_wu247_biao10')">
<div class="st-num">${want.wu247_biao10?.count||0}</div>
<div class="st-label">无247/天马标十</div>
<div class="st-price">均 ${want.wu247_biao10?.avg||0}元</div>
</div>
<div class="st-card" onclick="goStatPosts('want_wu247_biaobai')">
<div class="st-num">${want.wu247_biaobai?.count||0}</div>
<div class="st-label">无247/天马标百/刀</div>
<div class="st-price">均 ${want.wu247_biaobai?.avg||0}元</div>
</div>
<div class="st-card" onclick="goStatPosts('want_wu47_biaobai')">
<div class="st-num">${want.wu47_biaobai?.count||0}</div>
<div class="st-label">无47标百/刀</div>
<div class="st-price">均 ${want.wu47_biaobai?.avg||0}元</div>
</div>
</div>
<div style="font-size:13px;font-weight:600;color:#2e7d32;margin:16px 0 10px">🔍 出售龙钞类</div>
<div class="stats-grid">
<div class="st-card st-deal" onclick="goStatPosts('deal_dai4_biao10')">
<div class="st-num">${deal.dai4_biao10?.count||0}</div>
<div class="st-label">带4标十</div>
<div class="st-price">均 ${deal.dai4_biao10?.avg||0}元</div>
</div>
<div class="st-card st-deal" onclick="goStatPosts('deal_wu47_biao10')">
<div class="st-num">${deal.wu47_biao10?.count||0}</div>
<div class="st-label">无47标十</div>
<div class="st-price">均 ${deal.wu47_biao10?.avg||0}元</div>
</div>
<div class="st-card st-deal" onclick="goStatPosts('deal_wu247_biao10')">
<div class="st-num">${deal.wu247_biao10?.count||0}</div>
<div class="st-label">无247/天马标十</div>
<div class="st-price">均 ${deal.wu247_biao10?.avg||0}元</div>
</div>
<div class="st-card st-deal" onclick="goStatPosts('deal_wu247_biaobai')">
<div class="st-num">${deal.wu247_biaobai?.count||0}</div>
<div class="st-label">无247/天马标百/刀</div>
<div class="st-price">均 ${deal.wu247_biaobai?.avg||0}元</div>
</div>
<div class="st-card st-deal" onclick="goStatPosts('deal_wu47_biaobai')">
<div class="st-num">${deal.wu47_biaobai?.count||0}</div>
<div class="st-label">无47标百/刀</div>
<div class="st-price">均 ${deal.wu47_biaobai?.avg||0}元</div>
</div>
</div>
</div>
<div class="tab">
<div class="tab-i" onclick="S.v='home';render()"><span>📊</span>首页</div>
<div class="tab-i" onclick="goList('')"><span>📋</span>帖子</div>
<div class="tab-i" onclick="goCollections()"><span>💎</span>藏品</div>
<div class="tab-i" onclick="goUsers()"><span>👤</span>用户</div>
<div class="tab-i on" onclick="S.v='stats';loadStats()"><span>📈</span>统计</div>
</div>
`
}
if (S.v === 'list') {
const ft = S.ft
app.innerHTML = `
<div class="header"><h1>📋 帖子列表</h1></div>
<div class="f-bar">
<div class="f-row">
<button class="f-btn${ft.type===''?' on':''}" onclick="setF('type','')">全部</button>
<button class="f-btn${ft.type==='deal'?' on':''}" onclick="setF('type','deal')">出售</button>
<button class="f-btn${ft.type==='want'?' on':''}" onclick="setF('type','want')">求购</button>
<button class="f-btn${ft.type==='other'?' on':''}" onclick="setF('type','other')">其他</button>
</div>
<div class="f-row" style="margin-top:8px">
<button class="f-btn${ft.cat===''?' on':''}" onclick="setF('cat','')">全品类</button>
<button class="f-btn${ft.cat==='龙钞'?' on':''}" onclick="setF('cat','龙钞')">龙钞</button>
<button class="f-btn${ft.cat==='蛇钞'?' on':''}" onclick="setF('cat','蛇钞')">蛇钞</button>
<button class="f-btn${ft.cat==='马钞'?' on':''}" onclick="setF('cat','马钞')">马钞</button>
<button class="f-btn${ft.cat==='其他'?' on':''}" onclick="setF('cat','其他')">其他</button>
</div>
</div>
<div class="list">
${S.loading?'<div class="loading">加载中...</div>':''}
${!S.loading&&S.posts.length===0?'<div class="empty">暂无数据</div>':''}
${!S.loading&&S.posts.length>0?S.posts.map(p=>`
<div class="post" onclick="showDetail(${p.id})">
<div class="post-title">${p.title||'无标题'}</div>
<div class="post-meta">
<span class="badge b-${p.post_type||'normal'}">${p.post_type==='deal'?'出售':p.post_type==='want'?'求购':'普通'}</span>
${p.has_lifebuoy?'<span class="badge b-lifebuoy">🔥大救生圈</span>':''}
${p.price?'<span class="price">'+p.price+'元</span>':''}
${p.category?'<span class="tm">'+p.category+'</span>':''}
${p.author_username?'<span class="author">'+p.author_username+'</span>':''}
<span class="tm">${fmtTime(p.post_time)}</span>
${p.instance_id?'<span class="tm">🖥️'+p.instance_id.replace('server_','S')+'</span>':''}
</div>
</div>
`).join(''):''}
${!S.loading&&S.posts.length>0?`
<div class="pager">
<button class="pg-btn"${S.pg<=1?' disabled':''} onclick="prevP()">上一页</button>
<span class="pg-info">${S.pg} / ${Math.max(1,Math.ceil(S.total/S.ps))} (共${S.total}条)</span>
<button class="pg-btn"${S.pg>=Math.ceil(S.total/S.ps)?' disabled':''} onclick="nextP()">下一页</button>
</div>
`:''}
</div>
<div class="tab">
<div class="tab-i" onclick="S.v='home';render()"><span>📊</span>首页</div>
<div class="tab-i on" onclick="goList('')"><span>📋</span>帖子</div>
<div class="tab-i" onclick="goCollections()"><span>💎</span>藏品</div>
<div class="tab-i" onclick="goUsers()"><span>👤</span>用户</div>
<div class="tab-i" onclick="S.v='stats';loadStats()"><span>📈</span>统计</div>
</div>
`
}
if (S.v === 'collection-detail' && S.curCollection) {
const c = S.curCollection
app.innerHTML = `
<div class="detail">
<div class="detail-hdr">
<button class="back" onclick="S.v='collections';render()">← 返回藏品</button>
<a class="detail-link" href="${c.post_url||'#'}" target="_blank">原文链接</a>
</div>
<div class="detail-body">
<div class="detail-title">${c.name || c.crown_code || '无名称'}</div>
<div class="sec">
<div class="sec-title">🏷️ 藏品信息</div>
<div class="info-g">
<div class="info-i"><span class="info-l">品类</span><span class="${c.category==='龙钞'?'green':c.category==='马钞'?'orange':c.category==='蛇钞'?'purple':'gray'}">${c.category||'其他'}</span></div>
<div class="info-i"><span class="info-l">系列</span><span class="info-v">${c.series||'-'}</span></div>
<div class="info-i"><span class="info-l">发行年份</span><span class="info-v">${c.issue_year||'-'}</span></div>
<div class="info-i"><span class="info-l">冠号</span><span class="info-v" style="font-size:16px;font-weight:600;color:#1976d2">${c.crown_code||'-'}</span></div>
</div>
</div>
<div class="sec">
<div class="sec-title">📝 描述</div>
<div class="content">${c.description||'暂无描述'}</div>
</div>
<div class="sec">
<div class="sec-title">🔗 关联信息</div>
<div class="info-g">
<div class="info-i"><span class="info-l">帖子ID</span><span class="info-v">${c.post_id||'-'}</span></div>
<div class="info-i"><span class="info-l">藏品ID</span><span class="info-v">${c.id||'-'}</span></div>
<div class="info-i"><span class="info-l">创建时间</span><span class="info-v">${c.created_at?new Date(c.created_at).toLocaleString('zh-CN'):'-'}</span></div>
<div class="info-i"><span class="info-l">更新时间</span><span class="info-v">${c.updated_at?new Date(c.updated_at).toLocaleString('zh-CN'):'-'}</span></div>
<div class="info-i"><span class="info-l">采集服务器</span><span class="info-v">${c.instance_id||'-'}</span></div>
</div>
</div>
<div style="margin-top:20px">
<button onclick="goCollectionPost('${c.post_id}')" style="width:100%;padding:12px;background:#1976d2;color:#fff;border:none;border-radius:8px;font-size:15px;cursor:pointer">查看关联帖子 →</button>
</div>
</div>
</div>
`
}
if (S.v === 'detail' && S.cur) {
const p = S.cur
app.innerHTML = `
<div class="detail">
<div class="detail-hdr">
<button class="back" onclick="S.v='list';render()">← 返回</button>
<a class="detail-link" href="${p.url||'#'}" target="_blank">原文链接</a>
</div>
<div class="detail-body">
<div class="detail-title">${p.title||'无标题'}</div>
<div class="sec">
<div class="sec-title">💰 价格信息</div>
<div class="info-g">
<div class="info-i">
<span class="info-l">价格</span>
<span class="info-v prime">${p.price?p.price+'元':'-'}</span>
</div>
<div class="info-i">
<span class="info-l">品类</span>
<span class="info-v">${p.category||'-'}</span>
</div>
<div class="info-i">
<span class="info-l">包装</span>
<span class="info-v">${p.special_types||'-'}</span>
</div>
<div class="info-i">
<span class="info-l">号码</span>
<span class="info-v">${p.number_features||'-'}</span>
</div>
</div>
</div>
${p.special_types?`
<div class="sec">
<div class="sec-title">📦 包装分类</div>
<div class="tag-l">
${p.special_types.split(/[,\/|]/).map(s=>`<span class="tag">${s.trim()}</span>`).join('')}
</div>
</div>
`:''}
${p.number_features?`
<div class="sec">
<div class="sec-title">🔢 号码分类</div>
<div class="tag-l">
${p.number_features.split(/[,\/|]/).map(s=>`<span class="tag">${s.trim()}</span>`).join('')}
</div>
</div>
`:''}
${p.special_tags?`
<div class="sec">
<div class="sec-title">🏷️ 特殊标识</div>
<div class="tag-l">
${p.special_tags.split(/[,\/|]/).map(s=>`<span class="tag">${s.trim()}</span>`).join('')}
</div>
</div>
`:''}
<div class="sec">
<div class="sec-title">👤 作者信息</div>
<div class="info-g">
<div class="info-i">
<span class="info-l">用户名</span>
<span class="info-v" style="color:#1976d2">${p.author_username||'-'}</span>
</div>
<div class="info-i">
<span class="info-l">联系方式</span>
<span class="info-v">${p.contact||'-'}</span>
</div>
</div>
</div>
<div class="sec">
<div class="sec-title">📊 帖子状态</div>
<div class="info-g">
<div class="info-i">
<span class="info-l">类型</span>
<span class="badge b-${p.post_type||'normal'}">${p.post_type==='deal'?'出售':p.post_type==='want'?'求购':'普通'}</span>
</div>
<div class="info-i">
<span class="info-l">大救生圈</span>
<span class="info-v">${p.has_lifebuoy?'🔥 是':'否'}</span>
</div>
<div class="info-i">
<span class="info-l">发帖时间</span>
<span class="info-v">${p.post_time?new Date(p.post_time).toLocaleString('zh-CN'):'-'}</span>
</div>
<div class="info-i">
<span class="info-l">回复数</span>
<span class="info-v">${p.reply_count||0}</span>
</div>
${p.instance_id?'<div class="info-i"><span class="info-l">来源</span><span class="info-v">🖥️ '+p.instance_id.replace('server_','服务器')+'</span></div>':''}
</div>
</div>
<div class="sec">
<div class="sec-title">📝 帖子内容</div>
<div class="content">${p.content||'暂无内容'}</div>
</div>
</div>
</div>
`
}
}
function goCollections() {
console.log('goCollections clicked')
S.v = 'collections'
loadCollections()
}
function goUsers() {
console.log('goUsers clicked')
S.v = 'users'
loadUsers()
}
async function loadCollections() {
console.log('loadCollections called, current S.v=', S.v)
try {
const res = await fetch('/api/v1/collections?page=1&page_size=100')
const json = await res.json()
console.log('collections API response code:', json.code, 'items:', (json.data&&json.data.items||[]).length)
if (json.code === 0) {
S.collections = json.data.items || []
render()
}
} catch(e) { console.error('loadCollections error:', e.message) }
}
async function loadUsers() {
console.log('loadUsers called, current S.v=', S.v)
try {
const res = await fetch('/api/v1/users?page=1&page_size=100')
const json = await res.json()
console.log('users API response code:', json.code, 'items:', (json.data&&json.data.items||[]).length)
if (json.code === 0) {
S.users = json.data.items || []
render()
}
} catch(e) { console.error('loadUsers error:', e.message) }
}
function showCollectionDetail(c) {
alert(['名称: '+(c.name||'-'), '品类: '+(c.category||'-'), '系列: '+(c.series||'-'),
'年份: '+(c.issue_year||'-'), '冠号: '+(c.crown_code||'-'), '描述: '+(c.description||'-')].join('\n'))
}
async function goCollectionDetail(collectionId) {
try {
const res = await fetch('/api/v1/collections/'+collectionId)
const json = await res.json()
if (json.code === 0) {
S.curCollection = json.data
S.v = 'collection-detail'
render()
} else {
alert('藏品不存在')
}
} catch(e) { alert('加载失败: '+e.message) }
}
async function goCollectionPost(postId) {
try {
const res = await fetch('/api/v1/yichens/posts/'+encodeURIComponent(postId))
const json = await res.json()
if (json.code === 0) {
S.cur = json.data
S.v = 'detail'
render()
} else {
alert('帖子不存在或已删除')
}
} catch(e) { alert('加载失败: '+e.message) }
}
async function loadStats() {
try {
const r = await fetch(API+'/yichens/statistics')
const d = await r.json()
if (d.code === 0) { S.stats = d.data }
const r2 = await fetch(API+'/yichens/report/categories')
const d2 = await r2.json()
if (d2.code === 0) { S.catReport = d2.data }
const r3 = await fetch(API+'/yichens/report/stats')
const d3 = await r3.json()
if (d3.code === 0) { S.statsData = d3.data }
render()
} catch(e) { console.error(e) }
}
async function loadPosts() {
S.loading = true; render()
try {
let url = API+'/yichens/posts?page='+S.pg+'&page_size='+S.ps
if (S.ft.type) url += '&post_type='+S.ft.type
if (S.ft.cat) url += '&category='+S.ft.cat
if (S.ft.lifebuoy === 'yes') url += '&has_lifebuoy=true'
if (S.ft.timeRange) url += '&time_range='+S.ft.timeRange
if (S.ft.statType) { S.loading = false; loadStatPosts(); return }
const r = await fetch(url)
const d = await r.json()
if (d.code === 0) { S.posts = d.data.items||[]; S.total = d.data.total||0 }
} catch(e) { console.error(e) }
S.loading = false; render()
}
function goList(type, timeRange, cat) {
S.ft = { type: '', cat: '', lifebuoy: '', timeRange: '' }
if (type === 'lifebuoy') S.ft.lifebuoy = 'yes'
else if (type) S.ft.type = type
if (timeRange) S.ft.timeRange = timeRange
if (cat) S.ft.cat = cat
S.pg = 1; S.v = 'list'; loadPosts()
S.collections = []; S.users = []; S.colCat = ''; S.colSearch = ''; S.userSearch = ''
}
function goStatPosts(statType) {
S.ft = { type: '', cat: '', lifebuoy: '', timeRange: '', statType: statType }
S.pg = 1; S.v = 'list'; loadStatPosts()
}
async function loadStatPosts() {
if (!S.ft.statType) return
S.loading = true; render()
try {
let url = API+'/yichens/report/posts?stat_type='+S.ft.statType+'&page='+S.pg+'&page_size='+S.ps
const r = await fetch(url)
const d = await r.json()
if (d.code === 0) { S.posts = d.data.items||[]; S.total = d.data.total||0 }
} catch(e) { console.error(e) }
S.loading = false; render()
}
function setF(key, val) {
if (key === 'type') { S.ft.type = val; S.ft.timeRange = '' }
if (key === 'cat') { S.ft.cat = val; S.ft.timeRange = '' }
S.pg = 1; loadPosts()
}
function showDetail(id) {
S.cur = S.posts.find(p => p.id === id)
S.v = 'detail'; render()
}
function prevP() { if (S.pg > 1) { S.pg--; loadPosts(); window.scrollTo(0,0) } }
function nextP() { S.pg++; loadPosts(); window.scrollTo(0,0) }
function fmtTime(ts) {
if (!ts) return '-'
const d = new Date(ts), n = new Date()
const diff = n - d
if (diff < 60000) return '刚刚'
if (diff < 3600000) return Math.floor(diff/60000)+'分钟前'
if (diff < 86400000) return Math.floor(diff/3600000)+'小时前'
if (diff < 604800000) return Math.floor(diff/86400000)+'天前'
return d.toLocaleDateString('zh-CN')
}
// init
loadStats()
</script>
</body>
</html>