This commit is contained in:
2025-11-28 21:20:40 +08:00
commit f940b95b67
73 changed files with 15721 additions and 0 deletions

447
templates/profile.html Normal file
View File

@@ -0,0 +1,447 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 短视频解析平台</title>
<link rel="stylesheet" href="/static/css/ui-components.css?v=3">
<style>
body {
background: linear-gradient(135deg, #f0f9ff 0%, #e0e7ff 100%);
min-height: 100vh;
padding: 2rem 1rem;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.page-title {
font-size: 1.75rem;
font-weight: 700;
color: var(--secondary-900);
}
.header-actions {
display: flex;
gap: 0.75rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border-radius: var(--radius-lg);
padding: 1.5rem;
box-shadow: var(--shadow-md);
}
.stat-label {
font-size: 0.875rem;
color: var(--text-muted);
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: var(--secondary-900);
}
.stat-value.primary {
color: var(--primary-600);
}
.stat-value.success {
color: var(--success);
}
.stat-value.warning {
color: var(--warning);
}
.info-card {
background: white;
border-radius: var(--radius-lg);
padding: 1.5rem;
box-shadow: var(--shadow-md);
margin-bottom: 2rem;
}
.info-card h3 {
font-size: 1.125rem;
font-weight: 600;
color: var(--secondary-900);
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border-color);
}
.info-row {
display: flex;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px solid var(--secondary-100);
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
color: var(--text-muted);
font-size: 0.875rem;
}
.info-value {
font-weight: 500;
color: var(--secondary-800);
}
.group-badge {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
}
.group-badge.normal {
background: var(--secondary-100);
color: var(--secondary-700);
}
.group-badge.vip {
background: linear-gradient(135deg, #fbbf24, #f59e0b);
color: white;
}
.group-badge.svip {
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
color: white;
}
.logs-card {
background: white;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.logs-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
}
.logs-header h3 {
font-size: 1.125rem;
font-weight: 600;
color: var(--secondary-900);
margin: 0;
}
.logs-table {
width: 100%;
border-collapse: collapse;
}
.logs-table th,
.logs-table td {
padding: 0.875rem 1rem;
text-align: left;
font-size: 0.875rem;
}
.logs-table th {
background: var(--secondary-50);
color: var(--text-muted);
font-weight: 500;
}
.logs-table tr:not(:last-child) td {
border-bottom: 1px solid var(--secondary-100);
}
.logs-table .platform {
font-weight: 500;
color: var(--secondary-800);
}
.logs-table .url {
color: var(--primary-600);
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
transition: color 0.2s;
}
.logs-table .url:hover {
color: var(--primary-700);
text-decoration: underline;
}
.status-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
}
.status-badge.success {
background: #dcfce7;
color: #166534;
}
.status-badge.failed {
background: #fee2e2;
color: #991b1b;
}
.empty-state {
text-align: center;
padding: 3rem;
color: var(--text-muted);
}
.progress-bar {
height: 8px;
background: var(--secondary-100);
border-radius: 4px;
overflow: hidden;
margin-top: 0.5rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
border-radius: 4px;
transition: width 0.3s ease;
}
@media (max-width: 640px) {
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.stats-grid {
grid-template-columns: 1fr 1fr;
}
.logs-table {
display: block;
overflow-x: auto;
}
}
</style>
</head>
<body>
<div class="container">
<div class="page-header">
<h1 class="page-title">个人中心</h1>
<div class="header-actions">
<a href="/" class="ui-btn ui-btn-secondary">返回首页</a>
<button class="ui-btn ui-btn-danger" onclick="logout()">退出登录</button>
</div>
</div>
<!-- 使用统计 -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">今日已用</div>
<div class="stat-value primary" id="todayUsed">-</div>
</div>
<div class="stat-card">
<div class="stat-label">今日剩余</div>
<div class="stat-value success" id="todayRemaining">-</div>
</div>
<div class="stat-card">
<div class="stat-label">每日限额</div>
<div class="stat-value" id="dailyLimit">-</div>
</div>
<div class="stat-card">
<div class="stat-label">累计解析</div>
<div class="stat-value" id="totalCount">-</div>
</div>
</div>
<!-- 用户信息 -->
<div class="info-card">
<h3>账户信息</h3>
<div class="info-row">
<span class="info-label">用户名</span>
<span class="info-value" id="username">-</span>
</div>
<div class="info-row">
<span class="info-label">邮箱</span>
<span class="info-value" id="email">-</span>
</div>
<div class="info-row">
<span class="info-label">用户组</span>
<span class="info-value" id="groupName">-</span>
</div>
<div class="info-row">
<span class="info-label">注册时间</span>
<span class="info-value" id="createdAt">-</span>
</div>
<div class="info-row">
<span class="info-label">今日使用进度</span>
<span class="info-value" id="usageText">-</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="usageProgress" style="width: 0%"></div>
</div>
</div>
<!-- 解析记录 -->
<div class="logs-card">
<div class="logs-header">
<h3>解析记录最近20条</h3>
</div>
<table class="logs-table">
<thead>
<tr>
<th>平台</th>
<th>视频链接</th>
<th>状态</th>
<th>耗时</th>
<th>时间</th>
</tr>
</thead>
<tbody id="logsBody">
<tr>
<td colspan="5" class="empty-state">加载中...</td>
</tr>
</tbody>
</table>
</div>
</div>
<script src="/static/js/ui-components.js"></script>
<script>
async function loadProfile() {
try {
const response = await fetch('/auth/api/profile');
const result = await response.json();
if (!result.success) {
if (response.status === 401) {
window.location.href = '/auth/login';
return;
}
throw new Error(result.message || '加载失败');
}
const data = result.data;
// 更新统计数据
document.getElementById('todayUsed').textContent = data.usage.today_used;
document.getElementById('todayRemaining').textContent = data.usage.today_remaining;
document.getElementById('dailyLimit').textContent = data.usage.daily_limit;
document.getElementById('totalCount').textContent = data.usage.total_parse_count;
// 更新用户信息
document.getElementById('username').textContent = data.user.username;
document.getElementById('email').textContent = data.user.email;
document.getElementById('createdAt').textContent = data.user.created_at || '-';
// 用户组徽章
const groupBadge = getGroupBadge(data.group.name);
document.getElementById('groupName').innerHTML = groupBadge;
// 使用进度
const usagePercent = data.usage.daily_limit > 0
? Math.round((data.usage.today_used / data.usage.daily_limit) * 100)
: 0;
document.getElementById('usageText').textContent = `${data.usage.today_used} / ${data.usage.daily_limit}`;
document.getElementById('usageProgress').style.width = `${Math.min(usagePercent, 100)}%`;
// 更新解析记录
const logsBody = document.getElementById('logsBody');
if (data.parse_logs.length === 0) {
logsBody.innerHTML = '<tr><td colspan="5" class="empty-state">暂无解析记录</td></tr>';
} else {
logsBody.innerHTML = data.parse_logs.map((log, index) => `
<tr>
<td class="platform">${getPlatformName(log.platform)}</td>
<td class="url" title="点击复制链接" data-url="${log.video_url}" onclick="copyUrlFromElement(this)">${log.video_url}</td>
<td><span class="status-badge ${log.status === 'success' ? 'success' : 'failed'}">${log.status === 'success' ? '成功' : '失败'}</span></td>
<td>${log.response_time ? (log.response_time / 1000).toFixed(2) + 's' : '-'}</td>
<td>${log.created_at}</td>
</tr>
`).join('');
}
} catch (error) {
UI.notify('加载失败: ' + error.message, 'error');
}
}
function getGroupBadge(groupName) {
const name = groupName.toLowerCase();
if (name.includes('svip')) {
return `<span class="group-badge svip">${groupName}</span>`;
} else if (name.includes('vip')) {
return `<span class="group-badge vip">${groupName}</span>`;
}
return `<span class="group-badge normal">${groupName}</span>`;
}
function getPlatformName(platform) {
const names = {
'douyin': '抖音',
'tiktok': 'TikTok',
'bilibili': 'B站'
};
return names[platform] || platform;
}
function copyUrlFromElement(element) {
const url = element.getAttribute('data-url');
if (!url) {
UI.notify('无法获取链接', 'error');
return;
}
navigator.clipboard.writeText(url).then(() => {
UI.notify('链接已复制', 'success');
}).catch(() => {
UI.notify('复制失败', 'error');
});
}
async function logout() {
try {
await fetch('/auth/logout', { method: 'POST' });
UI.notify('已退出登录', 'success');
setTimeout(() => {
window.location.href = '/';
}, 1000);
} catch (error) {
UI.notify('退出失败', 'error');
}
}
loadProfile();
</script>
</body>
</html>