接口跑通,基础功能全部实现

This commit is contained in:
2026-03-16 16:14:08 +08:00
parent f81aec48ca
commit 2f2d5c3795
38 changed files with 3352 additions and 1754 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
{% extends "base.html" %}
{% block title %}Page Not Found - Weibo-HotSign{% endblock %}
{% block title %}页面未找到 - 微博超话签到{% endblock %}
{% block content %}
<div style="text-align: center; padding: 60px 20px;">
<h1 style="font-size: 48px; color: #6366f1; margin-bottom: 20px;">404</h1>
<p style="font-size: 24px; color: #333; margin-bottom: 20px;">Page Not Found</p>
<p style="color: #999; margin-bottom: 30px;">The page you're looking for doesn't exist.</p>
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">Go to Dashboard</a>
<div style="text-align:center; padding:80px 20px;">
<div style="font-size:64px; margin-bottom:16px;">🔍</div>
<h1 style="font-size:48px; font-weight:700; background:linear-gradient(135deg,#6366f1,#a855f7); -webkit-background-clip:text; -webkit-text-fill-color:transparent; margin-bottom:12px;">404</h1>
<p style="font-size:18px; color:#64748b; margin-bottom:32px;">页面未找到</p>
<a href="{{ url_for('dashboard') }}" class="btn btn-primary" style="padding:14px 32px; font-size:16px;">返回控制台</a>
</div>
{% endblock %}

View File

@@ -1,12 +1,12 @@
{% extends "base.html" %}
{% block title %}Server Error - Weibo-HotSign{% endblock %}
{% block title %}服务器错误 - 微博超话签到{% endblock %}
{% block content %}
<div style="text-align: center; padding: 60px 20px;">
<h1 style="font-size: 48px; color: #dc3545; margin-bottom: 20px;">500</h1>
<p style="font-size: 24px; color: #333; margin-bottom: 20px;">Server Error</p>
<p style="color: #999; margin-bottom: 30px;">Something went wrong on our end.</p>
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">Go to Dashboard</a>
<div style="text-align:center; padding:80px 20px;">
<div style="font-size:64px; margin-bottom:16px;">⚠️</div>
<h1 style="font-size:48px; font-weight:700; color:#ef4444; margin-bottom:12px;">500</h1>
<p style="font-size:18px; color:#64748b; margin-bottom:32px;">服务器出了点问题,请稍后再试</p>
<a href="{{ url_for('dashboard') }}" class="btn btn-primary" style="padding:14px 32px; font-size:16px;">返回控制台</a>
</div>
{% endblock %}

View File

@@ -1,170 +1,163 @@
{% extends "base.html" %}
{% block title %}Account Detail - Weibo-HotSign{% endblock %}
{% block title %}账号详情 - 微博超话签到{% endblock %}
{% block extra_css %}
<style>
.detail-header {
display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;
}
.detail-header h1 { font-size: 24px; font-weight: 700; color: #1e293b; }
.info-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-bottom: 24px;
}
@media (max-width: 768px) { .info-grid { grid-template-columns: 1fr; } }
.info-table td { padding: 12px 0; border-bottom: 1px solid #f1f5f9; font-size: 14px; }
.info-table td:first-child { font-weight: 600; color: #64748b; width: 30%; }
.action-btn {
width: 100%; padding: 12px; border-radius: 14px; border: none;
font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s;
text-align: center; text-decoration: none; display: block;
}
.action-btn-primary {
background: linear-gradient(135deg, #6366f1, #818cf8); color: white;
box-shadow: 0 2px 8px rgba(99,102,241,0.25);
}
.action-btn-primary:hover { box-shadow: 0 4px 16px rgba(99,102,241,0.35); transform: translateY(-1px); }
.action-btn-secondary { background: #f1f5f9; color: #475569; }
.action-btn-secondary:hover { background: #e2e8f0; }
.task-row {
display: flex; align-items: center; justify-content: space-between;
padding: 14px 0; border-bottom: 1px solid #f1f5f9;
}
.task-row:last-child { border-bottom: none; }
.task-cron {
font-family: 'SF Mono', Monaco, monospace; background: #eef2ff; color: #6366f1;
padding: 4px 12px; border-radius: 10px; font-size: 13px; font-weight: 600;
}
.task-actions { display: flex; gap: 8px; }
.task-actions .btn { padding: 6px 14px; font-size: 12px; border-radius: 10px; }
.log-row {
display: grid; grid-template-columns: 1fr auto auto auto; gap: 16px;
align-items: center; padding: 12px 0; border-bottom: 1px solid #f1f5f9; font-size: 14px;
}
.log-row:last-child { border-bottom: none; }
</style>
{% endblock %}
{% block content %}
<div style="max-width: 1000px; margin: 0 auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h1>{{ account.weibo_user_id }}</h1>
<div style="display: flex; gap: 10px;">
<a href="{{ url_for('edit_account', account_id=account.id) }}" class="btn btn-secondary">Edit</a>
<form method="POST" action="{{ url_for('delete_account', account_id=account.id) }}" style="display: inline;" onsubmit="return confirm('Are you sure?');">
<button type="submit" class="btn btn-danger">Delete</button>
<div style="max-width: 960px; margin: 0 auto;">
<div class="detail-header">
<div>
<h1>{{ account.remark or account.weibo_user_id }}</h1>
<div style="color:#94a3b8; font-size:14px; margin-top:4px;">UID: {{ account.weibo_user_id }}</div>
</div>
<div style="display:flex; gap:10px;">
<a href="{{ url_for('edit_account', account_id=account.id) }}" class="btn btn-secondary">✏️ 编辑</a>
<form method="POST" action="{{ url_for('delete_account', account_id=account.id) }}" style="display:inline;" onsubmit="return confirm('确定要删除此账号吗?');">
<button type="submit" class="btn btn-danger">删除</button>
</form>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px;">
<div class="info-grid">
<div class="card">
<div class="card-header">Account Info</div>
<table>
<div class="card-header">📋 账号信息</div>
<table class="info-table" style="width:100%;">
<tr>
<td style="font-weight: 500; width: 30%;">Status</td>
<td>状态</td>
<td>
{% if account.status == 'active' %}
<span class="badge badge-success">Active</span>
{% elif account.status == 'pending' %}
<span class="badge badge-warning">Pending</span>
{% elif account.status == 'invalid_cookie' %}
<span class="badge badge-danger">Invalid Cookie</span>
{% elif account.status == 'banned' %}
<span class="badge badge-danger">Banned</span>
{% 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 %}
</td>
</tr>
<tr>
<td style="font-weight: 500;">Remark</td>
<td>{{ account.remark or '-' }}</td>
</tr>
<tr>
<td style="font-weight: 500;">Created</td>
<td>{{ account.created_at[:10] }}</td>
</tr>
<tr>
<td style="font-weight: 500;">Last Checked</td>
<td>{{ account.last_checked_at[:10] if account.last_checked_at else '-' }}</td>
</tr>
<tr><td>备注</td><td>{{ account.remark or '-' }}</td></tr>
<tr><td>添加时间</td><td>{{ account.created_at[:10] }}</td></tr>
<tr><td>上次检查</td><td>{{ account.last_checked_at[:10] if account.last_checked_at else '-' }}</td></tr>
</table>
</div>
<div class="card">
<div class="card-header">Quick Actions</div>
<div style="display: flex; flex-direction: column; gap: 10px;">
<a href="{{ url_for('add_task', account_id=account.id) }}" class="btn btn-primary" style="text-align: center;">+ Add Task</a>
<a href="{{ url_for('edit_account', account_id=account.id) }}" class="btn btn-secondary" style="text-align: center;">Update Cookie</a>
<div class="card-header">⚡ 快捷操作</div>
<div style="display:flex; flex-direction:column; gap:10px;">
<form method="POST" action="{{ url_for('verify_account', account_id=account.id) }}">
<button type="submit" class="action-btn action-btn-secondary">🔍 验证 Cookie</button>
</form>
<form method="POST" action="{{ url_for('manual_signin', account_id=account.id) }}" onsubmit="this.querySelector('button').disabled=true; this.querySelector('button').textContent='⏳ 签到中...';">
<button type="submit" class="action-btn action-btn-primary">🚀 立即签到</button>
</form>
<a href="{{ url_for('add_task', account_id=account.id) }}" class="action-btn action-btn-secondary">⏰ 添加定时任务</a>
</div>
</div>
</div>
<div class="card">
<div class="card-header">Tasks</div>
<div class="card-header">⏰ 定时任务</div>
{% if tasks %}
<table>
<thead>
<tr>
<th>Cron Expression</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for task in tasks %}
<tr>
<td>{{ task.cron_expression }}</td>
<td>
{% if task.is_enabled %}
<span class="badge badge-success">Enabled</span>
{% else %}
<span class="badge badge-warning">Disabled</span>
{% endif %}
</td>
<td>{{ task.created_at[:10] }}</td>
<td>
<form method="POST" action="{{ url_for('toggle_task', task_id=task.id) }}" style="display: inline;">
<input type="hidden" name="account_id" value="{{ account.id }}">
<input type="hidden" name="is_enabled" value="{{ task.is_enabled|lower }}">
<button type="submit" class="btn btn-secondary" style="padding: 6px 12px; font-size: 12px;">
{% if task.is_enabled %}Disable{% else %}Enable{% endif %}
</button>
</form>
<form method="POST" action="{{ url_for('delete_task', task_id=task.id) }}" style="display: inline;" onsubmit="return confirm('Are you sure?');">
<input type="hidden" name="account_id" value="{{ account.id }}">
<button type="submit" class="btn btn-danger" style="padding: 6px 12px; font-size: 12px;">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% for task in tasks %}
<div class="task-row">
<div style="display:flex; align-items:center; gap:12px;">
<span class="task-cron">{{ task.cron_expression }}</span>
{% if task.is_enabled %}<span class="badge badge-success">已启用</span>
{% else %}<span class="badge badge-warning">已禁用</span>{% endif %}
</div>
<div class="task-actions">
<form method="POST" action="{{ url_for('toggle_task', task_id=task.id) }}" style="display:inline;">
<input type="hidden" name="account_id" value="{{ account.id }}">
<input type="hidden" name="is_enabled" value="{{ task.is_enabled|lower }}">
<button type="submit" class="btn btn-secondary">{% if task.is_enabled %}禁用{% else %}启用{% endif %}</button>
</form>
<form method="POST" action="{{ url_for('delete_task', task_id=task.id) }}" style="display:inline;" onsubmit="return confirm('确定要删除此任务吗?');">
<input type="hidden" name="account_id" value="{{ account.id }}">
<button type="submit" class="btn btn-danger">删除</button>
</form>
</div>
</div>
{% endfor %}
{% else %}
<p style="color: #999; text-align: center; padding: 20px;">No tasks yet</p>
<p style="color:#94a3b8; text-align:center; padding:24px; font-size:14px;">暂无定时任务</p>
{% endif %}
</div>
<div class="card">
<div class="card-header">Signin Logs</div>
{% if logs.items %}
<table>
<thead>
<tr>
<th>Topic</th>
<th>Status</th>
<th>Reward</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{% for log in logs.items %}
<tr>
<td>{{ log.topic_title or '-' }}</td>
<td>
{% if log.status == 'success' %}
<span class="badge badge-success">Success</span>
{% elif log.status == 'failed_already_signed' %}
<span class="badge badge-info">Already Signed</span>
{% elif log.status == 'failed_network' %}
<span class="badge badge-warning">Network Error</span>
{% elif log.status == 'failed_banned' %}
<span class="badge badge-danger">Banned</span>
{% endif %}
</td>
<td>
{% if log.reward_info %}
{{ log.reward_info.get('points', '-') }} pts
{% else %}
-
{% endif %}
</td>
<td>{{ log.signed_at[:10] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if logs.total > logs.size %}
<div class="pagination">
{% if logs.page > 1 %}
<a href="?page=1">First</a>
<a href="?page={{ logs.page - 1 }}">Previous</a>
{% endif %}
{% for p in range(max(1, logs.page - 2), min(logs.total // logs.size + 2, logs.page + 3)) %}
{% if p == logs.page %}
<span class="active">{{ p }}</span>
{% else %}
<a href="?page={{ p }}">{{ p }}</a>
<div class="card-header">📝 签到记录</div>
{% if logs['items'] %}
{% for log in logs['items'] %}
<div class="log-row">
<div style="font-weight:500; color:#1e293b;">{{ log.topic_title or '-' }}</div>
<div>
{% if log.status == 'success' %}<span class="badge badge-success">签到成功</span>
{% elif log.status == 'failed_already_signed' %}<span class="badge badge-info">今日已签</span>
{% elif log.status == 'failed_network' %}<span class="badge badge-warning">网络错误</span>
{% elif log.status == 'failed_banned' %}<span class="badge badge-danger">已封禁</span>
{% endif %}
</div>
<div style="font-size:13px; color:#64748b;">
{% if log.reward_info %}{{ log.reward_info.get('points', '-') }} 经验{% else %}-{% endif %}
</div>
<div style="color:#94a3b8; font-size:13px;">{{ log.signed_at[:10] }}</div>
</div>
{% endfor %}
{% if logs['total'] > logs['size'] %}
<div class="pagination">
{% if logs['page'] > 1 %}
<a href="?page=1">首页</a>
<a href="?page={{ logs['page'] - 1 }}">上一页</a>
{% endif %}
{% for p in range(max(1, logs['page'] - 2), min(logs['total'] // logs['size'] + 2, logs['page'] + 3)) %}
{% if p == logs['page'] %}<span class="active">{{ p }}</span>
{% else %}<a href="?page={{ p }}">{{ p }}</a>{% endif %}
{% endfor %}
{% if logs.page < logs.total // logs.size + 1 %}
<a href="?page={{ logs.page + 1 }}">Next</a>
<a href="?page={{ logs.total // logs.size + 1 }}">Last</a>
{% if logs['page'] < logs['total'] // logs['size'] + 1 %}
<a href="?page={{ logs['page'] + 1 }}">下一页</a>
<a href="?page={{ logs['total'] // logs['size'] + 1 }}">末页</a>
{% endif %}
</div>
{% endif %}
{% else %}
<p style="color: #999; text-align: center; padding: 20px;">No signin logs yet</p>
<p style="color:#94a3b8; text-align:center; padding:24px; font-size:14px;">暂无签到记录</p>
{% endif %}
</div>
</div>

View File

@@ -1,444 +1,149 @@
{% extends "base.html" %}
{% block title %}Add Account - Weibo-HotSign{% endblock %}
{% block title %}添加账号 - 微博超话签到{% endblock %}
{% block extra_css %}
<style>
.tab-container {
display: flex;
gap: 10px;
margin-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: none;
background: none;
font-size: 16px;
color: #666;
transition: all 0.3s;
}
.tab.active {
color: #6366f1;
border-bottom: 2px solid #6366f1;
margin-bottom: -2px;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.help-text {
background: #f8f9fa;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
font-size: 14px;
color: #666;
}
.help-text h4 {
margin-top: 0;
color: #333;
font-size: 16px;
}
.help-text ol, .help-text ul {
margin: 10px 0;
padding-left: 20px;
}
.help-text li {
margin: 8px 0;
line-height: 1.6;
}
.help-text code {
background: #e9ecef;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
font-size: 13px;
}
.method-card {
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
margin-bottom: 15px;
transition: all 0.3s;
}
.method-card:hover {
border-color: #6366f1;
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.1);
}
.method-title {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.method-badge {
.qr-wrapper { text-align: center; padding: 40px 20px; }
.qr-box {
background: #f8fafc;
padding: 28px;
border-radius: 24px;
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
margin-left: 10px;
border: 1.5px solid #e2e8f0;
margin-bottom: 20px;
}
.badge-recommended {
background: #d4edda;
color: #155724;
}
.warning-box {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px;
margin: 20px 0;
border-radius: 4px;
}
.warning-box h4 {
color: #856404;
margin-top: 0;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
pre {
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
font-size: 13px;
.qr-box img { width: 220px; height: 220px; display: block; margin: 0 auto 14px; border-radius: 12px; }
#qr-timer { color: #94a3b8; font-size: 14px; }
#qr-status { margin-top: 18px; font-size: 16px; min-height: 24px; font-weight: 500; }
.status-waiting { color: #64748b; }
.status-scanned { color: #f59e0b; }
.status-success { color: #10b981; }
.status-error { color: #ef4444; }
.info-box {
background: #f8fafc; border-radius: 16px; padding: 18px 22px; margin-top: 24px;
font-size: 14px; color: #64748b; text-align: left; max-width: 480px;
margin-left: auto; margin-right: auto; border: 1px solid #e2e8f0;
}
.info-box li { margin: 6px 0; line-height: 1.7; }
</style>
{% endblock %}
{% block content %}
<div style="max-width: 800px; margin: 0 auto;">
<h1 style="margin-bottom: 30px;">Add Weibo Account</h1>
<div style="max-width: 560px; margin: 0 auto;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:24px;">
<h1 style="font-size:24px; font-weight:700; color:#1e293b;">📱 添加微博账号</h1>
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">返回</a>
</div>
<div class="card">
<div class="tab-container">
<button class="tab active" onclick="switchTab('guide')">获取教程</button>
<button class="tab" onclick="switchTab('qrcode')">扫码添加</button>
<button class="tab" onclick="switchTab('manual')">手动添加</button>
</div>
<!-- 获取教程 Tab -->
<div id="guide-tab" class="tab-content active">
<h3 style="margin-bottom: 20px;">如何获取微博 Cookie</h3>
<div class="method-card">
<div class="method-title">
方法一:使用浏览器开发者工具
<span class="method-badge badge-recommended">推荐</span>
<div class="qr-wrapper">
<button id="generate-qr-btn" class="btn btn-primary" style="font-size:18px; padding:16px 40px; border-radius:18px;">
📱 生成二维码
</button>
<div id="qr-display" style="display:none;">
<div class="qr-box">
<img id="qr-image" src="" alt="QR Code">
<p style="color:#64748b; margin:0 0 4px;">请使用微博 APP 扫描</p>
<p id="qr-timer">有效期: 3:00</p>
</div>
<div class="help-text">
<ol>
<li>在浏览器中打开 <a href="https://weibo.com" target="_blank">https://weibo.com</a> 并登录</li>
<li><code>F12</code> 打开开发者工具</li>
<li>切换到 <code>Network</code> (网络) 标签</li>
<li>刷新页面 (<code>F5</code>)</li>
<li>在请求列表中点击任意一个请求(通常是第一个)</li>
<li>在右侧找到 <code>Request Headers</code> (请求头)</li>
<li>找到 <code>Cookie:</code> 字段,复制整行内容</li>
<li>切换到"添加账号"标签页,粘贴 Cookie</li>
</ol>
</div>
</div>
<div class="warning-box">
<h4>⚠️ 重要提示</h4>
<ul>
<li>Cookie 包含你的登录凭证,请妥善保管</li>
<li>不要在公共场合或不信任的网站输入 Cookie</li>
<li>Cookie 会被加密存储在数据库中</li>
<li>如果 Cookie 失效,系统会提示你更新</li>
<li>建议使用小号或测试账号,避免主账号风险</li>
</ul>
</div>
<div style="text-align: center; margin-top: 30px;">
<button class="btn btn-primary" onclick="switchTab('qrcode')" style="margin-right: 10px;">
使用扫码添加 →
</button>
<button class="btn btn-secondary" onclick="switchTab('manual')">
手动添加账号 →
</button>
<div id="qr-status" class="status-waiting">等待扫码...</div>
</div>
</div>
<!-- 扫码添加 Tab -->
<div id="qrcode-tab" class="tab-content">
<h3 style="margin-bottom: 20px;">微博扫码登录</h3>
<div class="method-card">
<div class="method-title">
扫码快速添加账号
<span class="method-badge badge-recommended">推荐</span>
</div>
<div class="help-text">
<p style="font-size: 16px; margin-bottom: 15px;">
使用微博网页版扫码登录,安全便捷地添加账号。
</p>
<h4>使用步骤:</h4>
<ol>
<li>点击下方"生成二维码"按钮</li>
<li>使用手机微博 APP 扫描二维码</li>
<li>在手机上点击"确认登录"</li>
<li>等待页面自动完成账号添加</li>
</ol>
</div>
</div>
<div id="qr-container" style="text-align: center; margin: 40px 0;">
<button id="generate-qr-btn" class="btn btn-primary" style="font-size: 18px; padding: 15px 40px;">
📱 生成二维码
</button>
<div id="qr-display" style="display: none; margin-top: 30px;">
<div style="background: white; padding: 20px; border-radius: 8px; display: inline-block; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
<img id="qr-image" src="" alt="QR Code" style="width: 256px; height: 256px; margin-bottom: 15px;">
<p style="color: #666; margin: 0;">请使用微博 APP 扫描</p>
<p id="qr-timer" style="color: #999; font-size: 14px; margin-top: 10px;">有效期: 3:00</p>
</div>
<div id="qr-status" style="margin-top: 20px; font-size: 16px; color: #666;">
等待扫码...
</div>
</div>
</div>
<div class="help-text">
<h4>💡 说明</h4>
<ul>
<li>使用微博网页版扫码登录接口,无需注册开放平台应用</li>
<li>扫码后自动获取登录 Cookie</li>
<li>Cookie 会被加密存储在数据库中</li>
<li>建议使用小号或测试账号,避免主账号风险</li>
</ul>
</div>
<div class="warning-box">
<h4>⚠️ 注意事项</h4>
<ul>
<li>二维码有效期 3 分钟,过期后需重新生成</li>
<li>扫码后请在手机上点击"确认登录"</li>
<li>如果长时间未响应,请刷新页面重试</li>
</ul>
</div>
</div>
<!-- 添加账号 Tab -->
<div id="manual-tab" class="tab-content">
<h3 style="margin-bottom: 20px;">手动添加账号</h3>
<form method="POST">
<input type="hidden" name="login_method" value="manual">
<div class="form-group">
<label for="weibo_user_id">Weibo User ID</label>
<input type="text" id="weibo_user_id" name="weibo_user_id" required placeholder="e.g., 123456789">
<small style="color: #999; display: block; margin-top: 8px;">
你的微博数字 ID可以在个人主页 URL 中找到
</small>
</div>
<div class="form-group">
<label for="cookie">Cookie</label>
<textarea id="cookie" name="cookie" required placeholder="Paste your Weibo cookie here" style="min-height: 150px;"></textarea>
<small style="color: #999; display: block; margin-top: 8px;">
粘贴从浏览器获取的完整 Cookie 字符串。Cookie 将被加密存储。
</small>
</div>
<div class="form-group">
<label for="remark">Remark (Optional)</label>
<input type="text" id="remark" name="remark" placeholder="e.g., My main account">
<small style="color: #999; display: block; margin-top: 8px;">
给这个账号添加备注,方便识别
</small>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">Add Account</button>
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Cancel</a>
</div>
</form>
<div class="help-text" style="margin-top: 30px;">
<h4>💡 快速提示</h4>
<p><strong>Weibo User ID 在哪里找?</strong></p>
<ol>
<li>登录微博后,点击右上角头像进入个人主页</li>
<li>查看浏览器地址栏,格式类似:<code>https://weibo.com/u/1234567890</code></li>
<li>最后的数字 <code>1234567890</code> 就是你的 User ID</li>
</ol>
</div>
<div class="info-box">
<p style="margin:0 0 8px; font-weight:600; color:#334155;">使用说明</p>
<ol style="margin:0; padding-left:20px;">
<li>点击"生成二维码"</li>
<li>打开手机微博 APP扫描二维码</li>
<li>在手机上点击"确认登录"</li>
<li>等待自动完成,跳转到控制台</li>
</ol>
</div>
</div>
</div>
<script>
function switchTab(tabName) {
// 更新 tab 按钮状态
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
event.target.classList.add('active');
let pollInterval = null, timerInterval = null, currentQrid = null, timeLeft = 180, isProcessing = false;
// 更新 tab 内容显示
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(tabName + '-tab').classList.add('active');
}
// 微博扫码登录相关代码
let pollInterval = null;
let timerInterval = null;
let currentQrid = null;
let timeLeft = 180; // 3分钟
document.addEventListener('DOMContentLoaded', function() {
const generateBtn = document.getElementById('generate-qr-btn');
if (generateBtn) {
generateBtn.addEventListener('click', async function() {
try {
// 生成二维码
const response = await fetch('/api/weibo/qrcode/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (!data.success) {
alert('生成二维码失败: ' + (data.error || '未知错误'));
return;
}
currentQrid = data.qrid;
const qrImage = data.qr_image;
// 显示二维码
document.getElementById('qr-image').src = qrImage;
document.getElementById('generate-qr-btn').style.display = 'none';
document.getElementById('qr-display').style.display = 'block';
document.getElementById('qr-status').textContent = '等待扫码...';
// 开始倒计时
timeLeft = 180;
updateTimer();
timerInterval = setInterval(updateTimer, 1000);
// 开始轮询状态
pollInterval = setInterval(checkQRStatus, 2000);
} catch (error) {
alert('生成二维码失败: ' + error.message);
}
});
}
document.getElementById('generate-qr-btn').addEventListener('click', async function() {
try {
const resp = await fetch('/api/weibo/qrcode/generate', {method:'POST', headers:{'Content-Type':'application/json'}});
const data = await resp.json();
if (!data.success) { alert('生成二维码失败: ' + (data.error || '未知错误')); return; }
currentQrid = data.qrid;
document.getElementById('qr-image').src = data.qr_image;
document.getElementById('generate-qr-btn').style.display = 'none';
document.getElementById('qr-display').style.display = 'block';
document.getElementById('qr-status').textContent = '等待扫码...';
document.getElementById('qr-status').className = 'status-waiting';
timeLeft = 180;
updateTimer();
timerInterval = setInterval(updateTimer, 1000);
pollInterval = setInterval(checkQRStatus, 2000);
} catch(e) { alert('生成二维码失败: ' + e.message); }
});
function updateTimer() {
if (timeLeft <= 0) {
clearInterval(timerInterval);
clearInterval(pollInterval);
document.getElementById('qr-status').innerHTML = '<span style="color: #dc3545;">二维码已过期,请重新生成</span>';
clearInterval(timerInterval); clearInterval(pollInterval);
document.getElementById('qr-status').textContent = '二维码已过期,请重新生成';
document.getElementById('qr-status').className = 'status-error';
document.getElementById('generate-qr-btn').style.display = 'inline-block';
document.getElementById('qr-display').style.display = 'none';
return;
}
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
document.getElementById('qr-timer').textContent =
`有效期: ${minutes}:${seconds.toString().padStart(2, '0')}`;
const m = Math.floor(timeLeft / 60), s = timeLeft % 60;
document.getElementById('qr-timer').textContent = `有效期: ${m}:${s.toString().padStart(2,'0')}`;
timeLeft--;
}
async function checkQRStatus() {
if (isProcessing) return;
isProcessing = true;
try {
const response = await fetch(`/api/weibo/qrcode/check/${currentQrid}`);
const data = await response.json();
const resp = await fetch(`/api/weibo/qrcode/check/${currentQrid}`);
const data = await resp.json();
if (data.status === 'waiting') {
document.getElementById('qr-status').textContent = '等待扫码...';
document.getElementById('qr-status').className = 'status-waiting';
} else if (data.status === 'scanned') {
document.getElementById('qr-status').innerHTML =
'<span style="color: #ffc107;">✓ 已扫码,请在手机上确认登录</span>';
document.getElementById('qr-status').textContent = '✓ 已扫码,请在手机上确认登录';
document.getElementById('qr-status').className = 'status-scanned';
} else if (data.status === 'success') {
// 扫码成功
clearInterval(pollInterval);
clearInterval(timerInterval);
document.getElementById('qr-status').innerHTML =
'<span style="color: #28a745;">✓ 登录成功!正在添加账号...</span>';
// 添加账号
const addResponse = await fetch('/api/weibo/qrcode/add-account', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ qrid: currentQrid })
clearInterval(pollInterval); pollInterval = null; clearInterval(timerInterval);
document.getElementById('qr-status').textContent = '✓ 登录成功,正在添加账号...';
document.getElementById('qr-status').className = 'status-success';
await new Promise(r => setTimeout(r, 500));
const addResp = await fetch('/api/weibo/qrcode/add-account', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({qrid: currentQrid})
});
const addResult = await addResponse.json();
const addResult = await addResp.json();
if (addResult.success) {
document.getElementById('qr-status').innerHTML =
'<span style="color: #28a745;">✓ 账号添加成功!正在跳转...</span>';
setTimeout(() => {
window.location.href = '/dashboard';
}, 1500);
document.getElementById('qr-status').textContent = '✓ 账号添加成功,正在跳转...';
setTimeout(() => { window.location.href = '/dashboard'; }, 1000);
} else if (addResult.need_login || addResp.status === 401) {
document.getElementById('qr-status').textContent = '登录已过期,正在跳转...';
document.getElementById('qr-status').className = 'status-error';
setTimeout(() => { window.location.href = '/login'; }, 1500);
} else {
document.getElementById('qr-status').innerHTML =
'<span style="color: #dc3545;">添加账号失败: ' + addResult.message + '</span>';
document.getElementById('qr-status').textContent = '添加失败: ' + addResult.message;
document.getElementById('qr-status').className = 'status-error';
}
} else if (data.status === 'expired') {
clearInterval(pollInterval);
clearInterval(timerInterval);
document.getElementById('qr-status').innerHTML =
'<span style="color: #dc3545;">二维码已过期,请重新生成</span>';
document.getElementById('generate-qr-btn').style.display = 'inline-block';
document.getElementById('qr-display').style.display = 'none';
} else if (data.status === 'cancelled') {
clearInterval(pollInterval);
clearInterval(timerInterval);
document.getElementById('qr-status').innerHTML =
'<span style="color: #dc3545;">已取消登录</span>';
return;
} else if (data.status === 'expired' || data.status === 'cancelled') {
clearInterval(pollInterval); pollInterval = null; clearInterval(timerInterval);
document.getElementById('qr-status').textContent = data.status === 'expired' ? '二维码已过期,请重新生成' : '已取消登录';
document.getElementById('qr-status').className = 'status-error';
document.getElementById('generate-qr-btn').style.display = 'inline-block';
document.getElementById('qr-display').style.display = 'none';
} else if (data.status === 'error') {
clearInterval(pollInterval);
clearInterval(timerInterval);
document.getElementById('qr-status').innerHTML =
'<span style="color: #dc3545;">错误: ' + (data.error || '未知错误') + '</span>';
clearInterval(pollInterval); pollInterval = null; clearInterval(timerInterval);
document.getElementById('qr-status').textContent = '错误: ' + (data.error || '未知错误');
document.getElementById('qr-status').className = 'status-error';
}
} catch (error) {
console.error('检查二维码状态失败:', error);
}
} catch(e) { console.error('检查状态失败:', e); }
finally { isProcessing = false; }
}
</script>
{% endblock %}

View File

@@ -1,30 +1,365 @@
{% extends "base.html" %}
{% block title %}Add Task - Weibo-HotSign{% endblock %}
{% block title %}添加任务 - 微博超话签到{% endblock %}
{% block extra_css %}
<style>
.task-container { max-width: 560px; margin: 0 auto; }
/* 时间轮盘 */
.wheel-section {
display: flex;
gap: 20px;
justify-content: center;
align-items: flex-start;
margin: 24px 0 28px;
}
.wheel-col { text-align: center; }
.wheel-col .wheel-label {
font-size: 13px;
font-weight: 600;
color: #94a3b8;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.wheel-wrapper {
position: relative;
width: 90px;
height: 180px;
overflow: hidden;
border-radius: 20px;
background: #f8fafc;
border: 1.5px solid #e2e8f0;
}
.wheel-wrapper::before, .wheel-wrapper::after {
content: '';
position: absolute;
left: 0; right: 0;
height: 60px;
z-index: 2;
pointer-events: none;
}
.wheel-wrapper::before {
top: 0;
background: linear-gradient(to bottom, rgba(248,250,252,1), rgba(248,250,252,0));
}
.wheel-wrapper::after {
bottom: 0;
background: linear-gradient(to top, rgba(248,250,252,1), rgba(248,250,252,0));
}
.wheel-highlight {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 78px; height: 44px;
border-radius: 14px;
background: linear-gradient(135deg, rgba(99,102,241,0.1), rgba(168,85,247,0.1));
border: 2px solid rgba(99,102,241,0.3);
z-index: 1;
pointer-events: none;
}
.wheel-scroll {
position: relative;
z-index: 1;
height: 100%;
overflow-y: scroll;
scroll-snap-type: y mandatory;
-ms-overflow-style: none;
scrollbar-width: none;
}
.wheel-scroll::-webkit-scrollbar { display: none; }
.wheel-item {
height: 44px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
font-weight: 600;
color: #cbd5e1;
scroll-snap-align: center;
transition: color 0.15s, transform 0.15s;
cursor: pointer;
user-select: none;
}
.wheel-item.active {
color: #6366f1;
transform: scale(1.1);
}
/* 重复模式 */
.repeat-options {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.repeat-chip {
padding: 8px 18px;
border-radius: 14px;
border: 1.5px solid #e2e8f0;
background: #fff;
font-size: 14px;
font-weight: 500;
color: #64748b;
cursor: pointer;
transition: all 0.2s;
user-select: none;
}
.repeat-chip:hover { border-color: #818cf8; color: #6366f1; }
.repeat-chip.selected {
background: linear-gradient(135deg, #6366f1, #818cf8);
color: white;
border-color: transparent;
box-shadow: 0 2px 8px rgba(99,102,241,0.25);
}
/* 星期选择 */
.weekday-row {
display: flex;
gap: 6px;
margin-top: 12px;
}
.weekday-btn {
width: 44px; height: 44px;
border-radius: 14px;
border: 1.5px solid #e2e8f0;
background: #fff;
font-size: 14px;
font-weight: 600;
color: #64748b;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.weekday-btn:hover { border-color: #818cf8; }
.weekday-btn.selected {
background: linear-gradient(135deg, #6366f1, #818cf8);
color: white;
border-color: transparent;
}
/* 预览 */
.cron-preview {
margin-top: 20px;
padding: 16px 20px;
background: #f8fafc;
border-radius: 16px;
border: 1.5px solid #e2e8f0;
font-size: 14px;
}
.cron-preview code {
display: inline-block;
background: #eef2ff;
color: #6366f1;
padding: 3px 10px;
border-radius: 8px;
font-family: 'SF Mono', Monaco, monospace;
font-size: 14px;
font-weight: 600;
}
.cron-preview .desc { color: #64748b; margin-top: 6px; }
</style>
{% endblock %}
{% block content %}
<div style="max-width: 600px; margin: 0 auto;">
<h1 style="margin-bottom: 30px;">Add Signin Task</h1>
<div class="task-container">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:24px;">
<h1 style="font-size:24px; font-weight:700; color:#1e293b;">⏰ 添加签到任务</h1>
<a href="{{ url_for('account_detail', account_id=account_id) }}" class="btn btn-secondary">返回</a>
</div>
<div class="card">
<form method="POST">
<div class="form-group">
<label for="cron_expression">Cron Expression</label>
<input type="text" id="cron_expression" name="cron_expression" required placeholder="e.g., 0 9 * * *">
<small style="color: #999; display: block; margin-top: 8px;">
Standard cron format (minute hour day month weekday)<br>
Examples:<br>
<code>0 9 * * *</code> - Every day at 9:00 AM<br>
<code>0 9 * * 1-5</code> - Weekdays at 9:00 AM<br>
<code>0 9,21 * * *</code> - Every day at 9:00 AM and 9:00 PM
</small>
</div>
<div class="card-header">选择签到时间</div>
<!-- 时间轮盘 -->
<div class="wheel-section">
<div class="wheel-col">
<div class="wheel-label"></div>
<div class="wheel-wrapper">
<div class="wheel-highlight"></div>
<div class="wheel-scroll" id="hour-wheel"></div>
</div>
</div>
<div style="font-size:32px; font-weight:700; color:#cbd5e1; padding-top:50px;">:</div>
<div class="wheel-col">
<div class="wheel-label"></div>
<div class="wheel-wrapper">
<div class="wheel-highlight"></div>
<div class="wheel-scroll" id="minute-wheel"></div>
</div>
</div>
</div>
<!-- 重复模式 -->
<div class="card-header" style="margin-top:8px;">重复方式</div>
<div class="repeat-options">
<div class="repeat-chip selected" data-mode="daily" onclick="setRepeat('daily')">每天</div>
<div class="repeat-chip" data-mode="weekdays" onclick="setRepeat('weekdays')">工作日</div>
<div class="repeat-chip" data-mode="custom" onclick="setRepeat('custom')">自定义</div>
</div>
<div id="weekday-picker" style="display:none; margin-top:12px;">
<div class="weekday-row">
<div class="weekday-btn" data-day="1" onclick="toggleDay(this)"></div>
<div class="weekday-btn" data-day="2" onclick="toggleDay(this)"></div>
<div class="weekday-btn" data-day="3" onclick="toggleDay(this)"></div>
<div class="weekday-btn" data-day="4" onclick="toggleDay(this)"></div>
<div class="weekday-btn" data-day="5" onclick="toggleDay(this)"></div>
<div class="weekday-btn" data-day="6" onclick="toggleDay(this)"></div>
<div class="weekday-btn" data-day="0" onclick="toggleDay(this)"></div>
</div>
</div>
<!-- Cron 预览 -->
<div class="cron-preview">
<div>生成的 Cron 表达式: <code id="cron-display">0 8 * * *</code></div>
<div class="desc" id="cron-desc">每天 08:00 执行签到</div>
</div>
<!-- 提交 -->
<form method="POST" id="task-form" style="margin-top:20px;">
<input type="hidden" id="cron_expression" name="cron_expression" value="0 8 * * *">
<div class="btn-group">
<button type="submit" class="btn btn-primary">Create Task</button>
<a href="{{ url_for('account_detail', account_id=account_id) }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary" style="flex:1; text-align:center;">创建任务</button>
<a href="{{ url_for('account_detail', account_id=account_id) }}" class="btn btn-secondary" style="text-align:center;">取消</a>
</div>
</form>
</div>
</div>
<script>
let selectedHour = 8;
let selectedMinute = 0;
let repeatMode = 'daily';
let customDays = new Set();
// 初始化轮盘
function initWheel(containerId, count, padFn, onSelect) {
const container = document.getElementById(containerId);
// 添加前后空白占位(让第一个和最后一个能滚到中间)
const padCount = 2;
for (let i = 0; i < padCount; i++) {
const spacer = document.createElement('div');
spacer.className = 'wheel-item';
spacer.style.visibility = 'hidden';
container.appendChild(spacer);
}
for (let i = 0; i < count; i++) {
const item = document.createElement('div');
item.className = 'wheel-item';
item.textContent = padFn(i);
item.dataset.value = i;
item.addEventListener('click', () => {
item.scrollIntoView({behavior:'smooth', block:'center'});
});
container.appendChild(item);
}
for (let i = 0; i < padCount; i++) {
const spacer = document.createElement('div');
spacer.className = 'wheel-item';
spacer.style.visibility = 'hidden';
container.appendChild(spacer);
}
// 滚动监听
let scrollTimer;
container.addEventListener('scroll', () => {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
const items = container.querySelectorAll('.wheel-item:not([style*="hidden"])');
const center = container.scrollTop + container.clientHeight / 2;
let closest = null, minDist = Infinity;
items.forEach(el => {
const dist = Math.abs(el.offsetTop + 22 - center);
if (dist < minDist) { minDist = dist; closest = el; }
});
if (closest && closest.dataset.value !== undefined) {
items.forEach(el => el.classList.remove('active'));
closest.classList.add('active');
onSelect(parseInt(closest.dataset.value));
}
}, 60);
});
return container;
}
function pad2(n) { return n.toString().padStart(2, '0'); }
const hourWheel = initWheel('hour-wheel', 24, pad2, v => { selectedHour = v; updateCron(); });
const minuteWheel = initWheel('minute-wheel', 60, pad2, v => { selectedMinute = v; updateCron(); });
// 初始滚动到默认值
setTimeout(() => {
scrollToValue(hourWheel, selectedHour);
scrollToValue(minuteWheel, selectedMinute);
}, 100);
function scrollToValue(container, value) {
const items = container.querySelectorAll('.wheel-item');
items.forEach(el => {
if (el.dataset.value == value) {
el.scrollIntoView({block:'center'});
el.classList.add('active');
}
});
}
function setRepeat(mode) {
repeatMode = mode;
document.querySelectorAll('.repeat-chip').forEach(c => c.classList.remove('selected'));
document.querySelector(`.repeat-chip[data-mode="${mode}"]`).classList.add('selected');
document.getElementById('weekday-picker').style.display = mode === 'custom' ? 'block' : 'none';
updateCron();
}
function toggleDay(el) {
const day = el.dataset.day;
if (customDays.has(day)) {
customDays.delete(day);
el.classList.remove('selected');
} else {
customDays.add(day);
el.classList.add('selected');
}
updateCron();
}
const dayNames = ['日','一','二','三','四','五','六'];
function updateCron() {
let dow = '*';
let desc = '';
if (repeatMode === 'daily') {
dow = '*';
desc = '每天';
} else if (repeatMode === 'weekdays') {
dow = '1-5';
desc = '工作日';
} else {
if (customDays.size === 0) {
dow = '*';
desc = '每天';
} else if (customDays.size === 7) {
dow = '*';
desc = '每天';
} else {
const sorted = [...customDays].sort((a,b) => a - b);
dow = sorted.join(',');
desc = '每周' + sorted.map(d => dayNames[d]).join('、');
}
}
const cron = `${selectedMinute} ${selectedHour} * * ${dow}`;
const timeStr = pad2(selectedHour) + ':' + pad2(selectedMinute);
document.getElementById('cron-display').textContent = cron;
document.getElementById('cron-desc').textContent = `${desc} ${timeStr} 执行签到`;
document.getElementById('cron_expression').value = cron;
}
</script>
{% endblock %}

View File

@@ -3,366 +3,215 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Weibo-HotSign{% endblock %}</title>
<title>{% block title %}微博超话签到{% endblock %}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
background-color: #f5f5f5;
color: #333;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
background: linear-gradient(135deg, #f0f2ff 0%, #faf5ff 50%, #fff0f6 100%);
color: #1a1a2e;
min-height: 100vh;
}
.navbar {
background-color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 0 20px;
background: rgba(255,255,255,0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(99,102,241,0.08);
padding: 0 24px;
position: sticky;
top: 0;
z-index: 100;
}
.navbar-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
height: 64px;
}
.navbar-brand {
font-size: 24px;
font-weight: bold;
color: #6366f1;
font-size: 22px;
font-weight: 700;
background: linear-gradient(135deg, #6366f1, #a855f7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-decoration: none;
}
.navbar-menu {
display: flex;
gap: 30px;
gap: 28px;
align-items: center;
}
.navbar-menu a {
color: #666;
.navbar-menu > a {
color: #64748b;
text-decoration: none;
transition: color 0.3s;
font-size: 15px;
font-weight: 500;
transition: color 0.2s;
padding: 6px 0;
}
.navbar-menu a:hover {
color: #6366f1;
}
.navbar-menu > a:hover { color: #6366f1; }
.navbar-user {
display: flex;
gap: 15px;
gap: 14px;
align-items: center;
}
.navbar-user span {
color: #475569;
font-size: 14px;
font-weight: 500;
}
.btn-logout {
background-color: #dc3545;
background: linear-gradient(135deg, #f43f5e, #e11d48);
color: white;
padding: 8px 16px;
padding: 7px 18px;
border: none;
border-radius: 4px;
border-radius: 20px;
cursor: pointer;
text-decoration: none;
transition: background-color 0.3s;
}
.btn-logout:hover {
background-color: #c82333;
font-size: 13px;
font-weight: 500;
transition: opacity 0.2s;
}
.btn-logout:hover { opacity: 0.85; }
.container {
max-width: 1200px;
margin: 30px auto;
margin: 28px auto;
padding: 0 20px;
}
.alert {
padding: 12px 20px;
padding: 14px 20px;
margin-bottom: 20px;
border-radius: 4px;
border-left: 4px solid;
}
.alert-success {
background-color: #d4edda;
color: #155724;
border-color: #28a745;
}
.alert-danger {
background-color: #f8d7da;
color: #721c24;
border-color: #f5c6cb;
}
.alert-warning {
background-color: #fff3cd;
color: #856404;
border-color: #ffeaa7;
}
.alert-info {
background-color: #d1ecf1;
color: #0c5460;
border-color: #bee5eb;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
border-radius: 16px;
font-size: 14px;
font-weight: 500;
color: #333;
}
.alert-success { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; }
.alert-danger { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }
.alert-warning { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }
.alert-info { background: #eff6ff; color: #1e40af; border: 1px solid #bfdbfe; }
input[type="text"],
input[type="email"],
input[type="password"],
textarea,
select {
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; font-weight: 600; color: #334155; font-size: 14px; }
input[type="text"], input[type="email"], input[type="password"], textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 12px 16px;
border: 1.5px solid #e2e8f0;
border-radius: 14px;
font-size: 14px;
font-family: inherit;
background: #fff;
transition: border-color 0.2s, box-shadow 0.2s;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
input[type="text"]:focus, input[type="email"]:focus, input[type="password"]:focus,
textarea:focus, select:focus {
outline: none;
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
textarea {
resize: vertical;
min-height: 100px;
border-color: #818cf8;
box-shadow: 0 0 0 4px rgba(99,102,241,0.1);
}
textarea { resize: vertical; min-height: 100px; }
.btn {
padding: 10px 20px;
padding: 10px 22px;
border: none;
border-radius: 4px;
border-radius: 14px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
font-weight: 600;
transition: all 0.2s;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background-color: #6366f1;
background: linear-gradient(135deg, #6366f1, #818cf8);
color: white;
box-shadow: 0 2px 8px rgba(99,102,241,0.25);
}
.btn-primary:hover {
background-color: #4f46e5;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
}
.btn-primary:hover { box-shadow: 0 4px 16px rgba(99,102,241,0.35); transform: translateY(-1px); }
.btn-secondary { background: #f1f5f9; color: #475569; }
.btn-secondary:hover { background: #e2e8f0; }
.btn-danger {
background-color: #dc3545;
background: linear-gradient(135deg, #f43f5e, #e11d48);
color: white;
box-shadow: 0 2px 8px rgba(244,63,94,0.25);
}
.btn-danger:hover {
background-color: #c82333;
}
.btn-danger:hover { box-shadow: 0 4px 16px rgba(244,63,94,0.35); }
.btn-success {
background-color: #28a745;
background: linear-gradient(135deg, #10b981, #059669);
color: white;
box-shadow: 0 2px 8px rgba(16,185,129,0.25);
}
.btn-success:hover {
background-color: #218838;
}
.btn-group {
display: flex;
gap: 10px;
margin-top: 20px;
}
.btn-success:hover { box-shadow: 0 4px 16px rgba(16,185,129,0.35); }
.btn-group { display: flex; gap: 10px; margin-top: 20px; }
.card {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 20px;
background: rgba(255,255,255,0.9);
backdrop-filter: blur(8px);
border-radius: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.04);
padding: 24px;
margin-bottom: 20px;
border: 1px solid rgba(255,255,255,0.6);
}
.card-header {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0;
font-size: 17px;
font-weight: 700;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #f1f5f9;
color: #1e293b;
}
.badge {
display: inline-block;
padding: 4px 12px;
padding: 4px 14px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
}
.badge-success {
background-color: #d4edda;
color: #155724;
}
.badge-warning {
background-color: #fff3cd;
color: #856404;
}
.badge-danger {
background-color: #f8d7da;
color: #721c24;
}
.badge-info {
background-color: #d1ecf1;
color: #0c5460;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
font-weight: 600;
}
.badge-success { background: #ecfdf5; color: #059669; }
.badge-warning { background: #fffbeb; color: #d97706; }
.badge-danger { background: #fef2f2; color: #dc2626; }
.badge-info { background: #eff6ff; color: #2563eb; }
table { width: 100%; border-collapse: collapse; margin-top: 12px; }
th {
background-color: #f8f9fa;
padding: 12px;
background: #f8fafc;
padding: 12px 16px;
text-align: left;
font-weight: 600;
border-bottom: 2px solid #dee2e6;
font-size: 13px;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 2px solid #e2e8f0;
}
td { padding: 14px 16px; border-bottom: 1px solid #f1f5f9; font-size: 14px; }
tr:hover { background: #f8fafc; }
td {
padding: 12px;
border-bottom: 1px solid #dee2e6;
}
tr:hover {
background-color: #f8f9fa;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.grid-item {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 20px;
cursor: pointer;
transition: all 0.3s;
}
.grid-item:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transform: translateY(-2px);
}
.grid-item-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.grid-item-subtitle {
font-size: 12px;
color: #999;
margin-bottom: 10px;
}
.grid-item-status {
margin-top: 10px;
}
.pagination {
display: flex;
gap: 5px;
justify-content: center;
margin-top: 20px;
}
.pagination a,
.pagination span {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
.pagination { display: flex; gap: 6px; justify-content: center; margin-top: 20px; }
.pagination a, .pagination span {
padding: 8px 14px;
border: 1.5px solid #e2e8f0;
border-radius: 12px;
text-decoration: none;
color: #6366f1;
font-size: 14px;
font-weight: 500;
}
.pagination a:hover {
background-color: #f0f0f0;
}
.pagination .active {
background-color: #6366f1;
color: white;
border-color: #6366f1;
}
.pagination .disabled {
color: #ccc;
cursor: not-allowed;
}
.pagination a:hover { background: #f1f5f9; }
.pagination .active { background: #6366f1; color: white; border-color: #6366f1; }
@media (max-width: 768px) {
.navbar-menu {
display: none;
}
.grid {
grid-template-columns: 1fr;
}
.btn-group {
flex-direction: column;
}
.btn {
width: 100%;
text-align: center;
}
.navbar-menu { display: none; }
.btn-group { flex-direction: column; }
.btn { width: 100%; text-align: center; }
}
</style>
{% block extra_css %}{% endblock %}
@@ -371,12 +220,12 @@
{% if session.get('user') %}
<nav class="navbar">
<div class="navbar-content">
<a href="{{ url_for('dashboard') }}" class="navbar-brand">Weibo-HotSign</a>
<a href="{{ url_for('dashboard') }}" class="navbar-brand">🔥 微博超话签到</a>
<div class="navbar-menu">
<a href="{{ url_for('dashboard') }}">Dashboard</a>
<a href="{{ url_for('dashboard') }}">控制台</a>
<div class="navbar-user">
<span>{{ session.get('user').get('username') }}</span>
<a href="{{ url_for('logout') }}" class="btn-logout">Logout</a>
<span>👤 {{ session.get('user').get('username') }}</span>
<a href="{{ url_for('logout') }}" class="btn-logout">退出</a>
</div>
</div>
</div>
@@ -391,7 +240,6 @@
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</body>

View File

@@ -1,40 +1,297 @@
{% extends "base.html" %}
{% block title %}Dashboard - Weibo-HotSign{% endblock %}
{% block title %}控制台 - 微博超话签到{% endblock %}
{% block extra_css %}
<style>
.dash-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28px;
}
.dash-header h1 {
font-size: 26px;
font-weight: 700;
color: #1e293b;
}
.dash-actions { display: flex; gap: 10px; }
/* 统计卡片 */
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 28px;
}
.stat-card {
background: rgba(255,255,255,0.9);
backdrop-filter: blur(8px);
border-radius: 20px;
padding: 22px 24px;
border: 1px solid rgba(255,255,255,0.6);
box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.04);
}
.stat-card .stat-icon { font-size: 28px; margin-bottom: 8px; }
.stat-card .stat-value { font-size: 28px; font-weight: 700; color: #1e293b; }
.stat-card .stat-label { font-size: 13px; color: #94a3b8; font-weight: 500; margin-top: 2px; }
/* 账号卡片网格 */
.account-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 18px;
}
.account-card {
background: rgba(255,255,255,0.92);
backdrop-filter: blur(8px);
border-radius: 20px;
padding: 24px;
border: 1px solid rgba(255,255,255,0.6);
box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.04);
cursor: pointer;
transition: all 0.25s ease;
position: relative;
overflow: hidden;
}
.account-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 4px;
border-radius: 20px 20px 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.status-banned::before { background: linear-gradient(90deg, #6b7280, #9ca3af); }
.account-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 30px rgba(99,102,241,0.12);
}
.account-card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 14px;
}
.account-avatar {
width: 48px; height: 48px;
border-radius: 16px;
background: linear-gradient(135deg, #6366f1, #a855f7);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
font-weight: 700;
flex-shrink: 0;
}
.account-name {
font-size: 16px;
font-weight: 700;
color: #1e293b;
margin-bottom: 4px;
word-break: break-all;
}
.account-remark {
font-size: 13px;
color: #94a3b8;
margin-bottom: 14px;
}
.account-meta {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 14px;
border-top: 1px solid #f1f5f9;
}
.account-date { font-size: 12px; color: #cbd5e1; }
/* 删除按钮 */
.account-del-btn {
width: 32px; height: 32px;
border-radius: 10px;
border: 1.5px solid #fecaca;
background: #fff;
color: #ef4444;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
}
.account-del-btn:hover {
background: #fef2f2;
border-color: #f87171;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 80px 20px;
background: rgba(255,255,255,0.7);
border-radius: 24px;
border: 2px dashed #e2e8f0;
}
.empty-state .empty-icon { font-size: 56px; margin-bottom: 16px; }
.empty-state p { color: #94a3b8; margin-bottom: 24px; font-size: 16px; }
/* Cookie 批量验证 */
.batch-verify-bar {
background: rgba(255,255,255,0.9);
backdrop-filter: blur(8px);
border-radius: 20px;
padding: 16px 24px;
margin-bottom: 20px;
border: 1px solid rgba(255,255,255,0.6);
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
display: flex;
justify-content: space-between;
align-items: center;
}
.batch-verify-bar .info { font-size: 14px; color: #64748b; }
.batch-verify-bar .info strong { color: #1e293b; }
</style>
{% endblock %}
{% block content %}
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h1>Weibo Accounts</h1>
<a href="{{ url_for('add_account') }}" class="btn btn-primary">+ Add Account</a>
<div class="dash-header">
<h1>👋 控制台</h1>
<div class="dash-actions">
<a href="{{ url_for('add_account') }}" class="btn btn-primary">+ 添加账号</a>
</div>
</div>
{% if accounts %}
<div class="grid">
{% for account in accounts %}
<div class="grid-item" onclick="window.location.href='{{ url_for('account_detail', account_id=account.id) }}'">
<div class="grid-item-title">{{ account.weibo_user_id }}</div>
<div class="grid-item-subtitle">{{ account.remark or 'No remark' }}</div>
<div class="grid-item-status">
<!-- 统计概览 -->
<div class="stats-row">
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-value">{{ accounts|length }}</div>
<div class="stat-label">账号总数</div>
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-value">{{ accounts|selectattr('status','equalto','active')|list|length }}</div>
<div class="stat-label">正常运行</div>
</div>
<div class="stat-card">
<div class="stat-icon">⚠️</div>
<div class="stat-value">{{ accounts|selectattr('status','equalto','pending')|list|length + accounts|selectattr('status','equalto','invalid_cookie')|list|length }}</div>
<div class="stat-label">需要关注</div>
</div>
</div>
<!-- 批量操作栏 -->
<div class="batch-verify-bar">
<div class="info">
💡 系统每天 <strong>23:50</strong> 自动批量验证 Cookie也可手动触发
</div>
<div style="display:flex; gap:10px;">
<button class="btn btn-secondary" id="batch-verify-btn" onclick="batchVerify()">🔍 批量验证 Cookie</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">Active</span>
<span class="badge badge-success">正常</span>
{% elif account.status == 'pending' %}
<span class="badge badge-warning">Pending</span>
<span class="badge badge-warning">待验证</span>
{% elif account.status == 'invalid_cookie' %}
<span class="badge badge-danger">Invalid Cookie</span>
<span class="badge badge-danger">Cookie 失效</span>
{% elif account.status == 'banned' %}
<span class="badge badge-danger">Banned</span>
<span class="badge badge-danger">已封禁</span>
{% endif %}
</div>
<div style="font-size: 12px; color: #999; margin-top: 10px;">
Created: {{ account.created_at[:10] }}
<div style="display:flex; align-items:center; gap:10px;">
<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>
{% endfor %}
</div>
{% endfor %}
</div>
{% else %}
<div class="card" style="text-align: center; padding: 60px 20px;">
<p style="color: #999; margin-bottom: 20px;">No accounts yet</p>
<a href="{{ url_for('add_account') }}" class="btn btn-primary">Add your first account</a>
</div>
<div class="empty-state">
<div class="empty-icon">📱</div>
<p>暂无账号,扫码添加你的微博账号开始自动签到</p>
<a href="{{ url_for('add_account') }}" class="btn btn-primary" style="font-size:16px; padding:14px 32px;">添加第一个账号</a>
</div>
{% endif %}
<script>
async function deleteAccount(accountId, name) {
if (!confirm(`确定要删除账号「${name}」吗?此操作不可恢复。`)) return;
try {
const form = document.createElement('form');
form.method = 'POST';
form.action = `/accounts/${accountId}/delete`;
document.body.appendChild(form);
form.submit();
} catch(e) {
alert('删除失败: ' + e.message);
}
}
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 = '🔍 批量验证 Cookie';
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 %}

View File

@@ -1,29 +1,24 @@
{% extends "base.html" %}
{% block title %}Edit Account - Weibo-HotSign{% endblock %}
{% block title %}编辑账号 - 微博超话签到{% endblock %}
{% block content %}
<div style="max-width: 600px; margin: 0 auto;">
<h1 style="margin-bottom: 30px;">Edit Account</h1>
<div style="max-width: 560px; margin: 0 auto;">
<h1 style="font-size:24px; font-weight:700; color:#1e293b; margin-bottom:24px;">✏️ 编辑账号</h1>
<div class="card">
<form method="POST">
<div class="form-group">
<label for="remark">Remark</label>
<input type="text" id="remark" name="remark" value="{{ account.remark or '' }}" placeholder="e.g., My main account">
<label for="remark">备注</label>
<input type="text" id="remark" name="remark" value="{{ account.remark or '' }}" placeholder="例如:我的主账号">
</div>
<div class="form-group">
<label for="cookie">Cookie (Leave empty to keep current)</label>
<textarea id="cookie" name="cookie" placeholder="Paste new cookie here if you want to update"></textarea>
<small style="color: #999; display: block; margin-top: 8px;">
Your cookie will be encrypted and stored securely.
</small>
<label for="cookie">Cookie(留空则保持不变)</label>
<textarea id="cookie" name="cookie" placeholder="如需更新 Cookie请在此粘贴新的 Cookie" style="border-radius:14px;"></textarea>
<small style="color:#94a3b8; display:block; margin-top:8px; font-size:13px;">Cookie 将被加密存储</small>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="{{ url_for('account_detail', account_id=account.id) }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary" style="flex:1; text-align:center;">保存修改</button>
<a href="{{ url_for('account_detail', account_id=account.id) }}" class="btn btn-secondary" style="text-align:center;">取消</a>
</div>
</form>
</div>

View File

@@ -1,69 +1,47 @@
{% extends "base.html" %}
{% block title %}Login - Weibo-HotSign{% endblock %}
{% block title %}登录 - 微博超话签到{% endblock %}
{% block extra_css %}
<style>
.auth-container {
max-width: 400px;
margin: 60px auto;
}
.auth-container { max-width: 420px; margin: 60px auto; }
.auth-card {
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 40px;
background: rgba(255,255,255,0.92);
backdrop-filter: blur(12px);
border-radius: 24px;
box-shadow: 0 4px 24px rgba(0,0,0,0.06);
padding: 44px 36px;
border: 1px solid rgba(255,255,255,0.6);
}
.auth-title {
text-align: center;
font-size: 28px;
font-weight: bold;
margin-bottom: 30px;
color: #6366f1;
}
.auth-link {
text-align: center;
margin-top: 20px;
color: #666;
}
.auth-link a {
color: #6366f1;
text-decoration: none;
font-weight: 500;
}
.auth-link a:hover {
text-decoration: underline;
text-align: center; font-size: 28px; font-weight: 700; margin-bottom: 8px;
background: linear-gradient(135deg, #6366f1, #a855f7);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
.auth-subtitle { text-align: center; color: #94a3b8; font-size: 15px; margin-bottom: 32px; }
.auth-link { text-align: center; margin-top: 24px; color: #94a3b8; font-size: 14px; }
.auth-link a { color: #6366f1; text-decoration: none; font-weight: 600; }
.auth-link a:hover { text-decoration: underline; }
</style>
{% endblock %}
{% block content %}
<div class="auth-container">
<div class="auth-card">
<h1 class="auth-title">Login</h1>
<h1 class="auth-title">🔥 微博超话签到</h1>
<p class="auth-subtitle">登录你的账号</p>
<form method="POST">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<label for="email">邮箱</label>
<input type="email" id="email" name="email" required placeholder="请输入邮箱">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
<label for="password">密码</label>
<input type="password" id="password" name="password" required placeholder="请输入密码">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%;">Login</button>
<button type="submit" class="btn btn-primary" style="width:100%; padding:14px; font-size:16px; border-radius:16px;">登录</button>
</form>
<div class="auth-link">
Don't have an account? <a href="{{ url_for('register') }}">Register</a>
</div>
<div class="auth-link">还没有账号?<a href="{{ url_for('register') }}">注册</a></div>
</div>
</div>
{% endblock %}

View File

@@ -1,133 +1,76 @@
{% extends "base.html" %}
{% block title %}Register - Weibo-HotSign{% endblock %}
{% block title %}注册 - 微博超话签到{% endblock %}
{% block extra_css %}
<style>
.auth-container {
max-width: 400px;
margin: 60px auto;
}
.auth-container { max-width: 420px; margin: 60px auto; }
.auth-card {
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 40px;
background: rgba(255,255,255,0.92);
backdrop-filter: blur(12px);
border-radius: 24px;
box-shadow: 0 4px 24px rgba(0,0,0,0.06);
padding: 44px 36px;
border: 1px solid rgba(255,255,255,0.6);
}
.auth-title {
text-align: center;
font-size: 28px;
font-weight: bold;
margin-bottom: 30px;
color: #6366f1;
}
.password-strength {
display: flex;
gap: 4px;
margin-top: 8px;
}
.strength-bar {
flex: 1;
height: 4px;
background-color: #ddd;
border-radius: 2px;
}
.strength-bar.active {
background-color: #28a745;
}
.strength-text {
font-size: 12px;
color: #666;
margin-top: 8px;
}
.auth-link {
text-align: center;
margin-top: 20px;
color: #666;
}
.auth-link a {
color: #6366f1;
text-decoration: none;
font-weight: 500;
}
.auth-link a:hover {
text-decoration: underline;
text-align: center; font-size: 28px; font-weight: 700; margin-bottom: 8px;
background: linear-gradient(135deg, #6366f1, #a855f7);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
.auth-subtitle { text-align: center; color: #94a3b8; font-size: 15px; margin-bottom: 32px; }
.strength-bar-row { display: flex; gap: 4px; margin-top: 8px; }
.strength-bar { flex: 1; height: 4px; background: #e2e8f0; border-radius: 4px; transition: background 0.3s; }
.strength-bar.active { background: #10b981; }
.auth-link { text-align: center; margin-top: 24px; color: #94a3b8; font-size: 14px; }
.auth-link a { color: #6366f1; text-decoration: none; font-weight: 600; }
.auth-link a:hover { text-decoration: underline; }
</style>
{% endblock %}
{% block content %}
<div class="auth-container">
<div class="auth-card">
<h1 class="auth-title">Create Account</h1>
<form method="POST" id="registerForm">
<h1 class="auth-title">🔥 微博超话签到</h1>
<p class="auth-subtitle">创建你的账号</p>
<form method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" required>
<label for="username">用户名</label>
<input type="text" id="username" name="username" required placeholder="请输入用户名">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<label for="email">邮箱</label>
<input type="email" id="email" name="email" required placeholder="请输入邮箱">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required onchange="checkPasswordStrength()">
<div class="password-strength" id="strengthBars">
<div class="strength-bar"></div>
<div class="strength-bar"></div>
<div class="strength-bar"></div>
<div class="strength-bar"></div>
<div class="strength-bar"></div>
</div>
<div class="strength-text">
Must contain: uppercase, lowercase, number, special character, 8+ chars
<label for="password">密码</label>
<input type="password" id="password" name="password" required oninput="checkStrength()">
<div class="strength-bar-row">
<div class="strength-bar"></div><div class="strength-bar"></div>
<div class="strength-bar"></div><div class="strength-bar"></div><div class="strength-bar"></div>
</div>
<small style="color:#94a3b8; font-size:12px; margin-top:6px; display:block;">需包含大小写字母、数字、特殊字符,至少 8 位</small>
</div>
<div class="form-group">
<label for="confirm_password">Confirm Password</label>
<input type="password" id="confirm_password" name="confirm_password" required>
<label for="confirm_password">确认密码</label>
<input type="password" id="confirm_password" name="confirm_password" required placeholder="再次输入密码">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%;">Register</button>
<button type="submit" class="btn btn-primary" style="width:100%; padding:14px; font-size:16px; border-radius:16px;">注册</button>
</form>
<div class="auth-link">
Already have an account? <a href="{{ url_for('login') }}">Login</a>
</div>
<div class="auth-link">已有账号?<a href="{{ url_for('login') }}">登录</a></div>
</div>
</div>
<script>
function checkPasswordStrength() {
const password = document.getElementById('password').value;
let strength = 0;
if (password.length >= 8) strength++;
if (/[a-z]/.test(password)) strength++;
if (/[A-Z]/.test(password)) strength++;
if (/\d/.test(password)) strength++;
if (/[!@#$%^&*]/.test(password)) strength++;
const bars = document.querySelectorAll('.strength-bar');
bars.forEach((bar, index) => {
if (index < strength) {
bar.classList.add('active');
} else {
bar.classList.remove('active');
}
function checkStrength() {
const pw = document.getElementById('password').value;
let s = 0;
if (pw.length >= 8) s++;
if (/[a-z]/.test(pw)) s++;
if (/[A-Z]/.test(pw)) s++;
if (/\d/.test(pw)) s++;
if (/[!@#$%^&*]/.test(pw)) s++;
document.querySelectorAll('.strength-bar').forEach((b, i) => {
b.classList.toggle('active', i < s);
});
}
</script>

View File

@@ -1,82 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>微博授权 - Weibo-HotSign</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
}
.container {
background: white;
border-radius: 12px;
padding: 40px;
max-width: 500px;
width: 100%;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
text-align: center;
}
.icon {
font-size: 64px;
margin-bottom: 20px;
}
.success { color: #28a745; }
.error { color: #dc3545; }
h1 {
color: #333;
margin-bottom: 15px;
font-size: 24px;
}
p {
color: #666;
line-height: 1.6;
margin-bottom: 20px;
}
.message {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin: 20px 0;
color: #333;
}
.close-btn {
background: #6366f1;
color: white;
border: none;
padding: 12px 30px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
.close-btn:hover {
background: #5558dd;
}
</style>
</head>
<body>
<div class="container">
{% if status == 'success' %}
<div class="icon success"></div>
<h1>授权成功!</h1>
<p>你的微博账号 <strong>{{ screen_name }}</strong> 已成功授权。</p>
<p>请返回电脑端页面完成账号添加。</p>
{% else %}
<div class="icon error"></div>
<h1>授权失败</h1>
<div class="message">{{ message }}</div>
<p>请返回电脑端页面重试。</p>
{% endif %}
<button class="close-btn" onclick="window.close()">关闭此页面</button>
</div>
</body>
</html>