feat: inline rename for profiles, dashboard stats grid UI

This commit is contained in:
2026-07-05 19:03:48 +08:00
parent d7850d200f
commit ee655be7d4
3 changed files with 56 additions and 13 deletions

View File

@@ -54,7 +54,9 @@ input[type="text"] + label, input[type="password"] + label, input[type="datetime
.filter-bar .form-row select { flex: 0 0 auto; width: auto; }
.inline-form { margin-bottom: 1.25rem; }
.rename-form { display: inline-flex; gap: .35rem; align-items: center; margin-right: .5rem; }
.rename-form input[type="text"] { width: 120px; flex: none; }
.nickname-display { display: inline; }
.nickname-edit { display: inline-flex; gap: .35rem; align-items: center; }
.nickname-edit input[type="text"] { width: 140px; flex: none; }
/* Buttons */
.btn { display: inline-block; padding: .5rem 1rem; border: 1px solid var(--border); border-radius: 6px; background: #fff; cursor: pointer; font-size: .9rem; text-align: center; white-space: nowrap; }
@@ -89,6 +91,16 @@ code { background: var(--bg); padding: .15rem .4rem; border-radius: 4px; font-si
.stats { display: flex; gap: .75rem; font-size: .85rem; color: var(--text-muted); flex-wrap: wrap; }
.stat { white-space: nowrap; }
/* Dashboard cards */
.dashboard-card { padding: 1.25rem; }
.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: .5rem; margin-top: .75rem; }
.stat-item { display: flex; flex-direction: column; align-items: center; gap: .1rem; }
.stat-num { font-size: 1.3rem; font-weight: 700; color: var(--text); }
.stat-label { font-size: .75rem; color: var(--text-muted); }
.stat-sent { color: var(--sent); }
.stat-delivered { color: var(--delivered); }
.stat-received { color: var(--received); }
/* Postcard grid */
.postcard-card { display: flex; gap: 1rem; background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: .75rem; transition: box-shadow .15s; }
.postcard-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.06); text-decoration: none; }

View File

@@ -2,20 +2,34 @@
{% block title %}Dashboard - Postcard Manager{% endblock %}
{% block content %}
<h1>👋 欢迎,{{ user.username }}</h1>
<section class="section">
<div class="section-header">
<h2>Profiles</h2>
<h2>我的 Profiles</h2>
<a href="/profiles" class="btn btn-sm">管理 Profiles</a>
</div>
{% if profiles %}
<div class="card-grid">
{% for p in profiles %}
<a href="/profiles/{{ p.id }}/postcards" class="card">
<a href="/profiles/{{ p.id }}/postcards" class="card dashboard-card">
<h3>{{ p.nickname }}</h3>
<div class="stats">
<span class="stat">📬 {{ stats[p.id].total }}</span>
<span class="stat sent">寄出 {{ stats[p.id].sent }}</span>
<span class="stat delivered">送达 {{ stats[p.id].delivered }}</span>
<span class="stat received">收到 {{ stats[p.id].received }}</span>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-num">{{ stats[p.id].total }}</span>
<span class="stat-label">总计</span>
</div>
<div class="stat-item">
<span class="stat-num stat-sent">{{ stats[p.id].sent }}</span>
<span class="stat-label">寄出</span>
</div>
<div class="stat-item">
<span class="stat-num stat-delivered">{{ stats[p.id].delivered }}</span>
<span class="stat-label">送达</span>
</div>
<div class="stat-item">
<span class="stat-num stat-received">{{ stats[p.id].received }}</span>
<span class="stat-label">收到</span>
</div>
</div>
</a>
{% endfor %}

View File

@@ -16,13 +16,18 @@
<tbody>
{% for p in profiles %}
<tr>
<td><a href="/profiles/{{ p.id }}/postcards">{{ p.nickname }}</a></td>
<td>
<span class="nickname-display" id="name-display-{{ p.id }}">{{ p.nickname }}</span>
<form method="post" action="/profiles/{{ p.id }}/rename" class="inline nickname-edit" id="name-edit-{{ p.id }}" style="display:none">
<input type="text" name="nickname" value="{{ p.nickname }}" required>
<button type="submit" class="btn btn-primary btn-sm">确定</button>
<button type="button" class="btn btn-sm" onclick="cancelRename({{ p.id }})">取消</button>
</form>
</td>
<td>{{ p.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<form method="post" action="/profiles/{{ p.id }}/rename" class="inline rename-form">
<input type="text" name="nickname" value="{{ p.nickname }}" required>
<button type="submit" class="btn btn-sm">改名</button>
</form>
<a href="/profiles/{{ p.id }}/postcards" class="btn btn-sm">查看</a>
<button type="button" class="btn btn-sm" onclick="startRename({{ p.id }})">改名</button>
<form method="post" action="/profiles/{{ p.id }}/delete" class="inline">
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('确定删除该 Profile 及其所有明信片?')">删除</button>
</form>
@@ -34,4 +39,16 @@
{% else %}
<p class="empty">还没有 Profile。</p>
{% endif %}
<script>
function startRename(id) {
document.getElementById('name-display-' + id).style.display = 'none';
var edit = document.getElementById('name-edit-' + id);
edit.style.display = 'inline-flex';
edit.querySelector('input').focus();
}
function cancelRename(id) {
document.getElementById('name-display-' + id).style.display = '';
document.getElementById('name-edit-' + id).style.display = 'none';
}
</script>
{% endblock %}