Files
weibo_signin/frontend/templates/dashboard.html
2026-03-18 09:51:27 +08:00

212 lines
9.6 KiB
HTML
Raw Permalink 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.
{% extends "base.html" %}
{% block title %}控制台 - 微博超话签到{% endblock %}
{% block extra_css %}
<style>
.dash-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.dash-header h1 { font-size: 22px; font-weight: 700; color: #1e293b; }
.stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 20px; }
.stat-card {
background: rgba(255,255,255,0.9); backdrop-filter: blur(8px);
border-radius: 16px; padding: 16px; border: 1px solid rgba(255,255,255,0.6);
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
.stat-card .stat-icon { font-size: 22px; margin-bottom: 4px; }
.stat-card .stat-value { font-size: 24px; font-weight: 700; color: #1e293b; }
.stat-card .stat-label { font-size: 12px; color: #94a3b8; font-weight: 500; }
.account-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
.account-card {
background: rgba(255,255,255,0.92); backdrop-filter: blur(8px);
border-radius: 16px; padding: 18px; border: 1px solid rgba(255,255,255,0.6);
box-shadow: 0 1px 3px rgba(0,0,0,0.04); cursor: pointer;
transition: all 0.2s; position: relative; overflow: hidden;
}
.account-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; border-radius: 16px 16px 0 0; }
.account-card.status-active::before { background: linear-gradient(90deg, #10b981, #34d399); }
.account-card.status-pending::before { background: linear-gradient(90deg, #f59e0b, #fbbf24); }
.account-card.status-invalid_cookie::before { background: linear-gradient(90deg, #ef4444, #f87171); }
.account-card:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(99,102,241,0.1); }
.account-card-top { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; }
.account-avatar {
width: 40px; height: 40px; border-radius: 12px;
background: linear-gradient(135deg, #6366f1, #a855f7);
display: flex; align-items: center; justify-content: center;
color: white; font-size: 16px; font-weight: 700; flex-shrink: 0;
}
.account-name { font-size: 15px; font-weight: 700; color: #1e293b; word-break: break-all; }
.account-remark { font-size: 12px; color: #94a3b8; margin-top: 2px; }
.account-meta {
display: flex; justify-content: space-between; align-items: center;
padding-top: 10px; border-top: 1px solid #f1f5f9;
}
.account-date { font-size: 11px; color: #cbd5e1; }
.account-del-btn {
width: 28px; height: 28px; border-radius: 8px; border: 1.5px solid #fecaca;
background: #fff; color: #ef4444; font-size: 12px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
}
.account-del-btn:hover { background: #fef2f2; }
.batch-bar {
background: rgba(255,255,255,0.9); border-radius: 14px; padding: 12px 16px;
margin-bottom: 14px; border: 1px solid rgba(255,255,255,0.6);
display: flex; justify-content: space-between; align-items: center; gap: 10px;
flex-wrap: wrap;
}
.batch-bar .info { font-size: 13px; color: #64748b; }
.empty-state {
text-align: center; padding: 60px 20px; background: rgba(255,255,255,0.7);
border-radius: 20px; border: 2px dashed #e2e8f0;
}
.empty-state .empty-icon { font-size: 48px; margin-bottom: 12px; }
.empty-state p { color: #94a3b8; margin-bottom: 20px; font-size: 15px; }
@media (max-width: 768px) {
.stats-row { grid-template-columns: repeat(3, 1fr); gap: 8px; }
.stat-card { padding: 12px; }
.stat-card .stat-value { font-size: 20px; }
.stat-card .stat-label { font-size: 11px; }
.account-grid { grid-template-columns: 1fr; }
.batch-bar { flex-direction: column; align-items: stretch; text-align: center; }
.batch-bar .info { margin-bottom: 8px; }
.dash-header { flex-direction: column; gap: 10px; align-items: flex-start; }
}
</style>
{% endblock %}
{% block content %}
<div class="dash-header">
<h1>👋 控制台</h1>
<a href="{{ url_for('add_account') }}" class="btn btn-primary">+ 添加账号</a>
</div>
{% set sc = pagination.get('status_counts', {}) %}
{% set total_accounts = pagination.get('total', 0) %}
{% set active_count = sc.get('active', 0) %}
{% set need_attention = sc.get('pending', 0) + sc.get('invalid_cookie', 0) %}
{% if total_accounts > 0 %}
<div class="stats-row">
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-value">{{ total_accounts }}</div>
<div class="stat-label">账号总数</div>
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-value">{{ active_count }}</div>
<div class="stat-label">正常运行</div>
</div>
<div class="stat-card">
<div class="stat-icon">⚠️</div>
<div class="stat-value">{{ need_attention }}</div>
<div class="stat-label">需要关注</div>
</div>
</div>
<div class="batch-bar">
<div class="info">💡 可手动触发批量操作</div>
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<button class="btn btn-secondary" id="batch-verify-btn" onclick="batchVerify()">🔍 批量验证</button>
<button class="btn btn-primary" id="batch-signin-btn" onclick="batchSignin()">🚀 全部签到</button>
</div>
</div>
<div class="account-grid">
{% for account in accounts %}
<div class="account-card status-{{ account.status }}" onclick="window.location.href='{{ url_for('account_detail', account_id=account.id) }}'">
<div class="account-card-top">
<div style="flex:1; min-width:0;">
<div class="account-name">{{ account.remark or account.weibo_user_id }}</div>
<div class="account-remark">UID: {{ account.weibo_user_id }}</div>
</div>
<div class="account-avatar">{{ (account.remark or account.weibo_user_id)[:1] }}</div>
</div>
<div class="account-meta">
<div>
{% if account.status == 'active' %}<span class="badge badge-success">正常</span>
{% elif account.status == 'pending' %}<span class="badge badge-warning">待验证</span>
{% elif account.status == 'invalid_cookie' %}<span class="badge badge-danger">Cookie 失效</span>
{% elif account.status == 'banned' %}<span class="badge badge-danger">已封禁</span>
{% endif %}
</div>
<div style="display:flex; align-items:center; gap:8px;">
<span class="account-date">{{ account.created_at[:10] }}</span>
<button class="account-del-btn" title="删除" onclick="event.stopPropagation(); deleteAccount('{{ account.id }}', '{{ account.remark or account.weibo_user_id }}');">🗑</button>
</div>
</div>
</div>
{% endfor %}
</div>
{% if pagination.get('total_pages', 0) > 1 %}
<div class="pagination">
{% set p = pagination.page %}
{% set tp = pagination.total_pages %}
{% if p > 1 %}
<a href="?page={{ p - 1 }}"> 上一页</a>
{% else %}
<span class="disabled"> 上一页</span>
{% endif %}
{% for i in range([1, p - 2]|max, [tp, p + 2]|min + 1) %}
{% if i == p %}<span class="active">{{ i }}</span>
{% else %}<a href="?page={{ i }}">{{ i }}</a>{% endif %}
{% endfor %}
{% if p < tp %}
<a href="?page={{ p + 1 }}">下一页 </a>
{% else %}
<span class="disabled">下一页 </span>
{% endif %}
</div>
{% endif %}
{% else %}
<div class="empty-state">
<div class="empty-icon">📱</div>
<p>暂无账号,扫码添加你的微博账号开始自动签到</p>
<a href="{{ url_for('add_account') }}" class="btn btn-primary" style="padding:12px 28px;">添加第一个账号</a>
</div>
{% endif %}
<script>
async function deleteAccount(id, name) {
if (!confirm(`确定要删除账号「${name}」吗?`)) return;
const form = document.createElement('form');
form.method = 'POST';
form.action = `/accounts/${id}/delete`;
document.body.appendChild(form);
form.submit();
}
async function batchVerify() {
const btn = document.getElementById('batch-verify-btn');
btn.disabled = true; btn.textContent = '⏳ 验证中...';
try {
const resp = await fetch('/api/batch/verify', {method: 'POST'});
const data = await resp.json();
if (data.success) {
const r = data.data;
alert(`验证完成:${r.valid} 有效,${r.invalid} 失效,${r.errors} 出错`);
} else { alert('验证失败: ' + (data.message || '未知错误')); }
} catch(e) { alert('请求失败: ' + e.message); }
btn.disabled = false; btn.textContent = '🔍 批量验证';
location.reload();
}
async function batchSignin() {
const btn = document.getElementById('batch-signin-btn');
if (!confirm('确定要对所有正常账号执行签到吗?')) return;
btn.disabled = true; btn.textContent = '⏳ 签到中...';
try {
const resp = await fetch('/api/batch/signin', {method: 'POST'});
const data = await resp.json();
if (data.success) {
const r = data.data;
alert(`签到完成:${r.total_accounts} 个账号,${r.total_signed} 成功,${r.total_already} 已签,${r.total_failed} 失败`);
} else { alert('签到失败: ' + (data.message || '未知错误')); }
} catch(e) { alert('请求失败: ' + e.message); }
btn.disabled = false; btn.textContent = '🚀 全部签到';
location.reload();
}
</script>
{% endblock %}