video-shuoshan/prototype/user-profile.html
zyc ffe92f7b15 Initial commit: 即梦视频生成平台
- web/: React + Vite + TypeScript 前端
- backend/: Django + DRF + SimpleJWT 后端
- prototype/: HTML 设计原型
- docs/: PRD 和设计评审文档
- test: 单元测试 + E2E 极限测试
2026-03-13 09:59:33 +08:00

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>