feat: inline rename for profiles, dashboard stats grid UI
This commit is contained in:
@@ -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; }
|
.filter-bar .form-row select { flex: 0 0 auto; width: auto; }
|
||||||
.inline-form { margin-bottom: 1.25rem; }
|
.inline-form { margin-bottom: 1.25rem; }
|
||||||
.rename-form { display: inline-flex; gap: .35rem; align-items: center; margin-right: .5rem; }
|
.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 */
|
/* 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; }
|
.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; }
|
.stats { display: flex; gap: .75rem; font-size: .85rem; color: var(--text-muted); flex-wrap: wrap; }
|
||||||
.stat { white-space: nowrap; }
|
.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 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 { 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; }
|
.postcard-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.06); text-decoration: none; }
|
||||||
|
|||||||
@@ -2,20 +2,34 @@
|
|||||||
{% block title %}Dashboard - Postcard Manager{% endblock %}
|
{% block title %}Dashboard - Postcard Manager{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>👋 欢迎,{{ user.username }}</h1>
|
<h1>👋 欢迎,{{ user.username }}</h1>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Profiles</h2>
|
<h2>我的 Profiles</h2>
|
||||||
|
<a href="/profiles" class="btn btn-sm">管理 Profiles</a>
|
||||||
</div>
|
</div>
|
||||||
{% if profiles %}
|
{% if profiles %}
|
||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
{% for p in profiles %}
|
{% 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>
|
<h3>{{ p.nickname }}</h3>
|
||||||
<div class="stats">
|
<div class="stats-grid">
|
||||||
<span class="stat">📬 {{ stats[p.id].total }}</span>
|
<div class="stat-item">
|
||||||
<span class="stat sent">寄出 {{ stats[p.id].sent }}</span>
|
<span class="stat-num">{{ stats[p.id].total }}</span>
|
||||||
<span class="stat delivered">送达 {{ stats[p.id].delivered }}</span>
|
<span class="stat-label">总计</span>
|
||||||
<span class="stat received">收到 {{ stats[p.id].received }}</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>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -16,13 +16,18 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for p in profiles %}
|
{% for p in profiles %}
|
||||||
<tr>
|
<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>{{ p.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||||
<td>
|
<td>
|
||||||
<form method="post" action="/profiles/{{ p.id }}/rename" class="inline rename-form">
|
<a href="/profiles/{{ p.id }}/postcards" class="btn btn-sm">查看</a>
|
||||||
<input type="text" name="nickname" value="{{ p.nickname }}" required>
|
<button type="button" class="btn btn-sm" onclick="startRename({{ p.id }})">改名</button>
|
||||||
<button type="submit" class="btn btn-sm">改名</button>
|
|
||||||
</form>
|
|
||||||
<form method="post" action="/profiles/{{ p.id }}/delete" class="inline">
|
<form method="post" action="/profiles/{{ p.id }}/delete" class="inline">
|
||||||
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('确定删除该 Profile 及其所有明信片?')">删除</button>
|
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('确定删除该 Profile 及其所有明信片?')">删除</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -34,4 +39,16 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<p class="empty">还没有 Profile。</p>
|
<p class="empty">还没有 Profile。</p>
|
||||||
{% endif %}
|
{% 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 %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user