feat(showcase): add public showcase page with per-profile/per-postcard visibility controls
This commit is contained in:
@@ -52,6 +52,7 @@
|
||||
<span class="profile-card-date">创建于 {{ p.created_at.strftime('%Y-%m-%d') }}</span>
|
||||
<div class="profile-card-actions">
|
||||
<a href="/profiles/{{ p.id }}/postcards" class="btn btn-primary btn-sm">查看明信片</a>
|
||||
<a href="/profiles/{{ p.id }}/showcase" class="btn btn-sm">展示管理</a>
|
||||
<form method="post" action="/profiles/{{ p.id }}/delete" class="inline">
|
||||
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('确定删除「{{ p.nickname }}」及其所有明信片?')">删除</button>
|
||||
</form>
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
<h1>用户设置</h1>
|
||||
</div>
|
||||
|
||||
<div class="form-card" style="margin-bottom:1.5rem">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between">
|
||||
<h3>展示设置</h3>
|
||||
<a href="/showcase/manage" class="btn btn-primary btn-sm">全局展示设置</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if success %}
|
||||
<div class="alert alert-success">{{ success }}</div>
|
||||
{% endif %}
|
||||
|
||||
123
app/templates/showcase.html
Normal file
123
app/templates/showcase.html
Normal file
@@ -0,0 +1,123 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ profile_user.username }} 的展示页 - 明信片管理器{% endblock %}
|
||||
{% block content %}
|
||||
<div class="section-header">
|
||||
<h1>📮 {{ profile_user.username }} 的明信片</h1>
|
||||
</div>
|
||||
|
||||
{% set has_content = (sent_all|length + received_all|length) > 0 %}
|
||||
{% if not has_content %}
|
||||
<div class="empty-state">
|
||||
<p>暂无展示内容</p>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<div class="tabs" id="tabs">
|
||||
<button class="tab active" onclick="switchTab('sent')">发出 ({{ sent_all|length }})</button>
|
||||
<button class="tab" onclick="switchTab('received')">收到 ({{ received_all|length }})</button>
|
||||
</div>
|
||||
|
||||
<div id="tab-sent" class="tab-panel">
|
||||
{% if mode == 'flat' %}
|
||||
<div class="showcase-grid">
|
||||
{% for pc in sent_all %}
|
||||
<div class="showcase-card">
|
||||
{% if pc.image_front %}
|
||||
<img src="{{ pc.image_front }}" alt="" class="showcase-card-img">
|
||||
{% else %}
|
||||
<div class="showcase-card-img placeholder-img">📷</div>
|
||||
{% endif %}
|
||||
<div class="showcase-card-body">
|
||||
<strong>{{ pc.card_number }}</strong>
|
||||
<div class="showcase-card-meta">
|
||||
<span>{{ pc.country|flag }} {{ pc.country or '' }}</span>
|
||||
{% if pc.send_time %}<span>{{ pc.send_time.strftime('%Y-%m-%d') }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% for g in profile_groups %}
|
||||
{% if g.sent %}
|
||||
<h3 style="margin:1rem 0 .75rem;font-size:.95rem;color:var(--text-muted)">{{ g.profile.nickname }}</h3>
|
||||
<div class="showcase-grid">
|
||||
{% for pc in g.sent %}
|
||||
<div class="showcase-card">
|
||||
{% if pc.image_front %}
|
||||
<img src="{{ pc.image_front }}" alt="" class="showcase-card-img">
|
||||
{% else %}
|
||||
<div class="showcase-card-img placeholder-img">📷</div>
|
||||
{% endif %}
|
||||
<div class="showcase-card-body">
|
||||
<strong>{{ pc.card_number }}</strong>
|
||||
<div class="showcase-card-meta">
|
||||
<span>{{ pc.country|flag }} {{ pc.country or '' }}</span>
|
||||
{% if pc.send_time %}<span>{{ pc.send_time.strftime('%Y-%m-%d') }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="tab-received" class="tab-panel" style="display:none">
|
||||
{% if mode == 'flat' %}
|
||||
<div class="showcase-grid">
|
||||
{% for pc in received_all %}
|
||||
<div class="showcase-card">
|
||||
{% if pc.image_front %}
|
||||
<img src="{{ pc.image_front }}" alt="" class="showcase-card-img">
|
||||
{% else %}
|
||||
<div class="showcase-card-img placeholder-img">📷</div>
|
||||
{% endif %}
|
||||
<div class="showcase-card-body">
|
||||
<strong>{{ pc.card_number }}</strong>
|
||||
<div class="showcase-card-meta">
|
||||
<span>{{ pc.country|flag }} {{ pc.country or '' }}</span>
|
||||
{% if pc.receive_time %}<span>{{ pc.receive_time.strftime('%Y-%m-%d') }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% for g in profile_groups %}
|
||||
{% if g.received %}
|
||||
<h3 style="margin:1rem 0 .75rem;font-size:.95rem;color:var(--text-muted)">{{ g.profile.nickname }}</h3>
|
||||
<div class="showcase-grid">
|
||||
{% for pc in g.received %}
|
||||
<div class="showcase-card">
|
||||
{% if pc.image_front %}
|
||||
<img src="{{ pc.image_front }}" alt="" class="showcase-card-img">
|
||||
{% else %}
|
||||
<div class="showcase-card-img placeholder-img">📷</div>
|
||||
{% endif %}
|
||||
<div class="showcase-card-body">
|
||||
<strong>{{ pc.card_number }}</strong>
|
||||
<div class="showcase-card-meta">
|
||||
<span>{{ pc.country|flag }} {{ pc.country or '' }}</span>
|
||||
{% if pc.receive_time %}<span>{{ pc.receive_time.strftime('%Y-%m-%d') }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function switchTab(name) {
|
||||
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
|
||||
document.querySelectorAll('.tab-panel').forEach(function(p) { p.style.display = 'none'; });
|
||||
event.currentTarget.classList.add('active');
|
||||
document.getElementById('tab-' + name).style.display = '';
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
78
app/templates/showcase_manage.html
Normal file
78
app/templates/showcase_manage.html
Normal file
@@ -0,0 +1,78 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}展示管理 - 明信片管理器{% endblock %}
|
||||
{% block content %}
|
||||
<div class="section-header">
|
||||
<div class="detail-title-row">
|
||||
<a href="/profiles" class="btn btn-back">←</a>
|
||||
<h1>展示管理</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-card">
|
||||
<h3 style="margin-bottom:.75rem">全局展示设置</h3>
|
||||
<form method="post" action="/showcase/manage">
|
||||
<label>展示页面模式</label>
|
||||
<select name="showcase_mode">
|
||||
<option value="profile" {% if user.showcase_mode == 'profile' %}selected{% endif %}>按 Profile 分类</option>
|
||||
<option value="flat" {% if user.showcase_mode == 'flat' %}selected{% endif %}>全部合并</option>
|
||||
</select>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">保存全局设置</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="form-hint" style="margin-top:.5rem;font-size:.8rem;color:var(--text-muted)">
|
||||
公开展示页地址:<a href="/u/{{ user.username }}" target="_blank">/u/{{ user.username }}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:1.5rem">
|
||||
<h2 style="font-size:1rem;margin-bottom:1rem">各 Profile 展示设置</h2>
|
||||
{% if profiles %}
|
||||
{% for p in profiles %}
|
||||
{% set pc_list = profile_cards.get(p.id, []) %}
|
||||
<div class="showcase-profile" style="background:var(--card-bg);border:1px solid var(--border);border-radius:10px;padding:1rem;margin-bottom:.75rem">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem">
|
||||
<strong>{{ p.nickname }}</strong>
|
||||
<span class="badge" style="{% if p.showcase_enabled %}background:var(--sent){% else %}background:var(--text-muted){% endif %}">{% if p.showcase_enabled %}展示中{% else %}未开启{% endif %}</span>
|
||||
</div>
|
||||
<form method="post" action="/profiles/{{ p.id }}/showcase" style="display:flex;flex-wrap:wrap;gap:.75rem;align-items:flex-end">
|
||||
<div style="display:flex;flex-direction:column;gap:.25rem">
|
||||
<label style="margin:0;font-size:.78rem">开启展示</label>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" name="showcase_enabled" value="1" {% if p.showcase_enabled %}checked{% endif %} onchange="this.form.submit()">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:.25rem">
|
||||
<label style="margin:0;font-size:.78rem">展示内容</label>
|
||||
<select name="showcase_visible" onchange="this.form.submit()">
|
||||
<option value="both" {% if p.showcase_visible == 'both' %}selected{% endif %}>发出 + 收到</option>
|
||||
<option value="sent" {% if p.showcase_visible == 'sent' %}selected{% endif %}>仅发出</option>
|
||||
<option value="received" {% if p.showcase_visible == 'received' %}selected{% endif %}>仅收到</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
{% if p.showcase_enabled and pc_list %}
|
||||
<div style="margin-top:.75rem;border-top:1px solid var(--border);padding-top:.75rem">
|
||||
<p style="font-size:.8rem;color:var(--text-muted);margin-bottom:.5rem">单张明信片展示控制(关闭展示后可单独隐藏):</p>
|
||||
{% for pc in pc_list %}
|
||||
<form method="post" action="/postcards/{{ pc.id }}/showcase-toggle" style="display:inline-flex;align-items:center;gap:.3rem;margin-right:.75rem;margin-bottom:.3rem;font-size:.8rem">
|
||||
<span>{{ pc.card_number }}</span>
|
||||
<label class="toggle-switch toggle-sm">
|
||||
<input type="checkbox" name="showcase_hidden" value="1" {% if pc.showcase_hidden %}checked{% endif %} onchange="this.form.submit()">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
{% if pc.showcase_hidden %}<span style="color:var(--text-muted);font-size:.75rem">隐藏</span>{% endif %}
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<p>暂无 Profile,请先创建</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user