- web/: React + Vite + TypeScript 前端 - backend/: Django + DRF + SimpleJWT 后端 - prototype/: HTML 设计原型 - docs/: PRD 和设计评审文档 - test: 单元测试 + E2E 极限测试
391 lines
16 KiB
HTML
391 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>个人中心 — Jimeng Clone</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
--bg-page: #0a0a0f; --bg-card: #16161e;
|
|
--border: #2a2a38; --primary: #00b8e6; --primary-dim: rgba(0,184,230,0.12);
|
|
--text-1: #ffffff; --text-2: #8a8a9a; --text-3: #4a4a5a;
|
|
--hover: rgba(255,255,255,0.06);
|
|
--success: #34d399; --danger: #f87171; --warning: #fbbf24;
|
|
}
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: 'Noto Sans SC', 'Space Grotesk', system-ui, sans-serif;
|
|
background: var(--bg-page); color: var(--text-1);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.page-header {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 16px 32px; border-bottom: 1px solid var(--border);
|
|
background: var(--bg-page); position: sticky; top: 0; z-index: 10;
|
|
backdrop-filter: blur(12px);
|
|
}
|
|
.page-header-left { display: flex; align-items: center; gap: 16px; }
|
|
.back-link {
|
|
display: flex; align-items: center; gap: 6px;
|
|
font-size: 13px; color: var(--text-2); text-decoration: none;
|
|
padding: 6px 12px; border-radius: 6px; transition: all 0.15s;
|
|
}
|
|
.back-link:hover { background: var(--hover); color: var(--text-1); }
|
|
.page-title { font-size: 18px; font-weight: 600; }
|
|
.page-header-right { display: flex; align-items: center; gap: 12px; }
|
|
.user-badge {
|
|
font-size: 13px; color: var(--primary); padding: 4px 12px;
|
|
background: var(--primary-dim); border-radius: 6px; font-weight: 500;
|
|
}
|
|
.logout-btn {
|
|
padding: 6px 14px; background: transparent; border: 1px solid var(--border);
|
|
border-radius: 6px; color: var(--text-2); font-size: 13px;
|
|
cursor: pointer; transition: all 0.15s;
|
|
}
|
|
.logout-btn:hover { background: var(--hover); color: var(--text-1); }
|
|
|
|
.page-content { max-width: 900px; margin: 0 auto; padding: 28px 32px; }
|
|
|
|
/* Overview cards */
|
|
.overview-card {
|
|
background: var(--bg-card); border: 1px solid var(--border);
|
|
border-radius: 12px; padding: 28px; margin-bottom: 24px;
|
|
}
|
|
.overview-title {
|
|
font-size: 15px; font-weight: 600; margin-bottom: 24px;
|
|
}
|
|
.overview-grid {
|
|
display: grid; grid-template-columns: 200px 1fr 1fr; gap: 24px;
|
|
align-items: center;
|
|
}
|
|
@media (max-width: 768px) {
|
|
.overview-grid { grid-template-columns: 1fr; }
|
|
}
|
|
|
|
/* Ring chart (SVG) */
|
|
.ring-chart { text-align: center; }
|
|
.ring-label { font-size: 12px; color: var(--text-2); margin-top: 8px; }
|
|
|
|
/* Quota card */
|
|
.quota-card {
|
|
background: rgba(255,255,255,0.02); border-radius: 10px;
|
|
padding: 20px;
|
|
}
|
|
.quota-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
|
.quota-title { font-size: 13px; color: var(--text-2); }
|
|
.quota-pct {
|
|
font-family: 'JetBrains Mono', monospace; font-size: 12px;
|
|
padding: 2px 8px; border-radius: 4px; font-weight: 500;
|
|
}
|
|
.pct-normal { color: var(--success); background: rgba(52,211,153,0.1); }
|
|
.pct-warning { color: var(--warning); background: rgba(251,191,36,0.1); }
|
|
.pct-danger { color: var(--danger); background: rgba(248,113,113,0.1); }
|
|
.quota-values {
|
|
display: flex; justify-content: space-between; align-items: baseline;
|
|
margin-bottom: 12px;
|
|
}
|
|
.quota-used {
|
|
font-family: 'JetBrains Mono', 'Space Grotesk', monospace;
|
|
font-size: 24px; font-weight: 700;
|
|
}
|
|
.quota-limit { font-size: 13px; color: var(--text-3); }
|
|
.progress-track {
|
|
height: 8px; background: rgba(255,255,255,0.06);
|
|
border-radius: 4px; overflow: hidden;
|
|
}
|
|
.progress-fill {
|
|
height: 100%; border-radius: 4px;
|
|
transition: width 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
.fill-normal { background: linear-gradient(90deg, var(--primary), #33ccf0); }
|
|
.fill-warning { background: linear-gradient(90deg, var(--warning), #f59e0b); }
|
|
|
|
/* Sparkline section */
|
|
.sparkline-card {
|
|
background: var(--bg-card); border: 1px solid var(--border);
|
|
border-radius: 12px; padding: 24px; margin-bottom: 24px;
|
|
}
|
|
.sparkline-header {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
margin-bottom: 16px;
|
|
}
|
|
.sparkline-title { font-size: 15px; font-weight: 600; }
|
|
.range-btns { display: flex; gap: 4px; }
|
|
.range-btn {
|
|
padding: 4px 12px; border: 1px solid var(--border);
|
|
border-radius: 6px; background: transparent; color: var(--text-2);
|
|
font-size: 12px; cursor: pointer; transition: all 0.15s;
|
|
}
|
|
.range-btn:hover { background: var(--hover); color: var(--text-1); }
|
|
.range-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }
|
|
|
|
/* Records list */
|
|
.records-card {
|
|
background: var(--bg-card); border: 1px solid var(--border);
|
|
border-radius: 12px; padding: 24px; margin-bottom: 24px;
|
|
}
|
|
.records-title { font-size: 15px; font-weight: 600; margin-bottom: 16px; }
|
|
.record-item {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 14px 16px; background: rgba(255,255,255,0.02);
|
|
border-radius: 8px; margin-bottom: 8px;
|
|
transition: background 0.15s;
|
|
}
|
|
.record-item:hover { background: rgba(255,255,255,0.04); }
|
|
.record-left { flex: 1; min-width: 0; }
|
|
.record-time { font-size: 12px; color: var(--text-3); margin-bottom: 3px; }
|
|
.record-prompt { font-size: 13px; color: var(--text-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.record-right { display: flex; align-items: center; gap: 12px; flex-shrink: 0; margin-left: 16px; }
|
|
.record-seconds {
|
|
font-family: 'JetBrains Mono', monospace; font-size: 14px;
|
|
color: var(--primary); font-weight: 500;
|
|
}
|
|
.record-mode {
|
|
display: inline-block; padding: 2px 8px; border-radius: 4px;
|
|
font-size: 11px; font-weight: 500;
|
|
}
|
|
.mode-universal { color: var(--primary); background: var(--primary-dim); }
|
|
.mode-keyframe { color: #a78bfa; background: rgba(167,139,250,0.12); }
|
|
.record-status { font-size: 12px; min-width: 40px; text-align: right; }
|
|
.status-done { color: var(--success); }
|
|
.status-failed { color: var(--danger); }
|
|
|
|
.load-more {
|
|
display: block; width: 100%; padding: 12px; border: 1px dashed var(--border);
|
|
border-radius: 8px; background: transparent; color: var(--text-2);
|
|
font-size: 13px; cursor: pointer; transition: all 0.15s; text-align: center;
|
|
}
|
|
.load-more:hover { background: var(--hover); color: var(--text-1); border-style: solid; }
|
|
|
|
/* Warning banner */
|
|
.quota-warning {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 12px 16px; border-radius: 8px; margin-bottom: 24px;
|
|
font-size: 13px;
|
|
}
|
|
.warning-yellow { background: rgba(251,191,36,0.08); border: 1px solid rgba(251,191,36,0.2); color: var(--warning); }
|
|
|
|
::-webkit-scrollbar { width: 4px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
|
@keyframes fadeUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
|
|
.animate-in { animation: fadeUp 0.4s ease-out forwards; opacity: 0; }
|
|
.delay-1 { animation-delay: 0.1s; }
|
|
.delay-2 { animation-delay: 0.2s; }
|
|
.delay-3 { animation-delay: 0.3s; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="page-header">
|
|
<div class="page-header-left">
|
|
<a href="video-generation.html" class="back-link">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
|
返回首页
|
|
</a>
|
|
<span class="page-title">个人中心</span>
|
|
</div>
|
|
<div class="page-header-right">
|
|
<span class="user-badge">zhang_wei</span>
|
|
<button class="logout-btn">退出</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="page-content">
|
|
<!-- Quota warning -->
|
|
<div class="quota-warning warning-yellow animate-in">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
|
今日额度已消费 82%,请合理安排使用
|
|
</div>
|
|
|
|
<!-- Consumption Overview -->
|
|
<div class="overview-card animate-in">
|
|
<div class="overview-title">消费概览</div>
|
|
<div class="overview-grid">
|
|
<!-- Ring chart -->
|
|
<div class="ring-chart">
|
|
<svg viewBox="0 0 120 120" width="160" height="160">
|
|
<!-- Background ring -->
|
|
<circle cx="60" cy="60" r="48" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="10"/>
|
|
<!-- Progress ring (57.5% = 345/600) -->
|
|
<circle cx="60" cy="60" r="48" fill="none" stroke="url(#ringGrad)" stroke-width="10"
|
|
stroke-dasharray="301.6" stroke-dashoffset="128.2"
|
|
stroke-linecap="round" transform="rotate(-90 60 60)"/>
|
|
<defs>
|
|
<linearGradient id="ringGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<stop offset="0%" stop-color="#00b8e6"/>
|
|
<stop offset="100%" stop-color="#33ccf0"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<!-- Center text -->
|
|
<text x="60" y="52" text-anchor="middle" fill="var(--text-1)" font-size="22" font-weight="700" font-family="JetBrains Mono, monospace">345<tspan font-size="11" fill="var(--text-2)">s</tspan></text>
|
|
<text x="60" y="70" text-anchor="middle" fill="var(--text-3)" font-size="11" font-family="Noto Sans SC">/ 600s 今日</text>
|
|
</svg>
|
|
<div class="ring-label">今日已用额度</div>
|
|
</div>
|
|
|
|
<!-- Daily quota -->
|
|
<div class="quota-card">
|
|
<div class="quota-header">
|
|
<span class="quota-title">今日额度</span>
|
|
<span class="quota-pct pct-warning">82.0%</span>
|
|
</div>
|
|
<div class="quota-values">
|
|
<span class="quota-used">345<span style="font-size:12px;color:var(--text-2);font-weight:400">s</span></span>
|
|
<span class="quota-limit">/ 600s</span>
|
|
</div>
|
|
<div class="progress-track">
|
|
<div class="progress-fill fill-warning" style="width:82%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Monthly quota -->
|
|
<div class="quota-card">
|
|
<div class="quota-header">
|
|
<span class="quota-title">本月额度</span>
|
|
<span class="quota-pct pct-normal">39.1%</span>
|
|
</div>
|
|
<div class="quota-values">
|
|
<span class="quota-used">2,345<span style="font-size:12px;color:var(--text-2);font-weight:400">s</span></span>
|
|
<span class="quota-limit">/ 6,000s</span>
|
|
</div>
|
|
<div class="progress-track">
|
|
<div class="progress-fill fill-normal" style="width:39.1%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sparkline trend -->
|
|
<div class="sparkline-card animate-in delay-1">
|
|
<div class="sparkline-header">
|
|
<span class="sparkline-title">消费趋势</span>
|
|
<div class="range-btns">
|
|
<button class="range-btn active">近7天</button>
|
|
<button class="range-btn">近30天</button>
|
|
</div>
|
|
</div>
|
|
<svg viewBox="0 0 700 100" style="width:100%;height:80px">
|
|
<defs>
|
|
<linearGradient id="sparkGrad" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stop-color="#00b8e6" stop-opacity="0.2"/>
|
|
<stop offset="100%" stop-color="#00b8e6" stop-opacity="0"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<!-- Area -->
|
|
<path d="M0,70 L100,55 L200,60 L300,35 L400,45 L500,30 L600,40 L700,25 L700,100 L0,100 Z" fill="url(#sparkGrad)"/>
|
|
<!-- Line -->
|
|
<path d="M0,70 L100,55 L200,60 L300,35 L400,45 L500,30 L600,40 L700,25" fill="none" stroke="#00b8e6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<!-- Dots -->
|
|
<circle cx="0" cy="70" r="3" fill="#00b8e6"/>
|
|
<circle cx="100" cy="55" r="3" fill="#00b8e6"/>
|
|
<circle cx="200" cy="60" r="3" fill="#00b8e6"/>
|
|
<circle cx="300" cy="35" r="3" fill="#00b8e6"/>
|
|
<circle cx="400" cy="45" r="3" fill="#00b8e6"/>
|
|
<circle cx="500" cy="30" r="3" fill="#00b8e6"/>
|
|
<circle cx="600" cy="40" r="3" fill="#00b8e6"/>
|
|
<circle cx="700" cy="25" r="3" fill="#00b8e6"/>
|
|
<!-- Labels -->
|
|
<text x="0" y="95" fill="#4a4a5a" font-size="10" font-family="JetBrains Mono">3/6</text>
|
|
<text x="100" y="95" fill="#4a4a5a" font-size="10" font-family="JetBrains Mono">3/7</text>
|
|
<text x="200" y="95" fill="#4a4a5a" font-size="10" font-family="JetBrains Mono">3/8</text>
|
|
<text x="300" y="95" fill="#4a4a5a" font-size="10" font-family="JetBrains Mono">3/9</text>
|
|
<text x="400" y="95" fill="#4a4a5a" font-size="10" font-family="JetBrains Mono">3/10</text>
|
|
<text x="500" y="95" fill="#4a4a5a" font-size="10" font-family="JetBrains Mono">3/11</text>
|
|
<text x="600" y="95" fill="#4a4a5a" font-size="10" font-family="JetBrains Mono">3/12</text>
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- Consumption Records -->
|
|
<div class="records-card animate-in delay-2">
|
|
<div class="records-title">消费记录</div>
|
|
|
|
<div class="record-item">
|
|
<div class="record-left">
|
|
<div class="record-time">2026-03-12 14:30</div>
|
|
<div class="record-prompt">一只猫在花园里追蝴蝶,阳光洒在草地上,蝴蝶翩翩飞舞</div>
|
|
</div>
|
|
<div class="record-right">
|
|
<span class="record-seconds">15s</span>
|
|
<span class="record-mode mode-universal">全能参考</span>
|
|
<span class="record-status status-done">完成</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="record-item">
|
|
<div class="record-left">
|
|
<div class="record-time">2026-03-12 13:15</div>
|
|
<div class="record-prompt">日落海边散步的情侣,浪花拍打沙滩,金色的余晖映照</div>
|
|
</div>
|
|
<div class="record-right">
|
|
<span class="record-seconds">10s</span>
|
|
<span class="record-mode mode-keyframe">首尾帧</span>
|
|
<span class="record-status status-done">完成</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="record-item">
|
|
<div class="record-left">
|
|
<div class="record-time">2026-03-12 10:42</div>
|
|
<div class="record-prompt">城市夜景延时摄影,灯火辉煌车流不息</div>
|
|
</div>
|
|
<div class="record-right">
|
|
<span class="record-seconds">5s</span>
|
|
<span class="record-mode mode-universal">全能参考</span>
|
|
<span class="record-status status-failed">失败</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="record-item">
|
|
<div class="record-left">
|
|
<div class="record-time">2026-03-11 18:30</div>
|
|
<div class="record-prompt">雪山上的雄鹰展翅飞翔,俯瞰壮丽山河</div>
|
|
</div>
|
|
<div class="record-right">
|
|
<span class="record-seconds">15s</span>
|
|
<span class="record-mode mode-universal">全能参考</span>
|
|
<span class="record-status status-done">完成</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="record-item">
|
|
<div class="record-left">
|
|
<div class="record-time">2026-03-11 16:15</div>
|
|
<div class="record-prompt">春天的樱花树下,花瓣随风飘落,少女转身微笑</div>
|
|
</div>
|
|
<div class="record-right">
|
|
<span class="record-seconds">10s</span>
|
|
<span class="record-mode mode-keyframe">首尾帧</span>
|
|
<span class="record-status status-done">完成</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="record-item">
|
|
<div class="record-left">
|
|
<div class="record-time">2026-03-11 14:00</div>
|
|
<div class="record-prompt">赛博朋克风格的未来城市,霓虹灯闪烁,飞车穿梭</div>
|
|
</div>
|
|
<div class="record-right">
|
|
<span class="record-seconds">15s</span>
|
|
<span class="record-mode mode-universal">全能参考</span>
|
|
<span class="record-status status-done">完成</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="load-more">加载更多</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.querySelectorAll('.range-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
document.querySelector('.range-btn.active')?.classList.remove('active');
|
|
btn.classList.add('active');
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|