cam / app /templates /admin_activity_edit.html
cacode's picture
Upload 60 files
26d04d9 verified
{% extends 'base.html' %}
{% block content %}
<section class="hero-card admin-hero">
<div>
<a class="ghost-link" href="/admin/activities">返回活动管理</a>
<p class="eyebrow">Edit Activity</p>
<h2>{{ activity.title }}</h2>
<p class="lead">在这里可以修改活动时间、排行榜可见性、任务内容和图床链接,并删除不再需要的任务。管理员任务图片仅保留链接方式,用户提交图片仍保存在 Docker 本地目录。</p>
</div>
<div class="hero-badges">
<span class="pill">创建人 {{ activity.created_by.display_name }}</span>
<span class="pill">{{ activity.tasks|length }} 个任务</span>
<span class="pill">开始 {{ activity.start_at|datetime_local }}</span>
<span class="pill">{{ '用户端可见' if activity.is_visible else '用户端隐藏' }}</span>
</div>
<div class="card-footer">
<button class="btn btn-danger" type="submit" form="delete-activity-form" onclick="return confirm('确定删除这个活动吗?相关任务、审核记录和本地提交图片都会一并删除。');">删除整个活动</button>
</div>
</section>
<form method="post" action="/admin/activities/{{ activity.id }}/edit" class="form-stack">
<section class="glass-card form-panel wide-panel">
<div class="section-head">
<div>
<p class="eyebrow">Activity Settings</p>
<h3>活动信息</h3>
</div>
</div>
<div class="form-grid cols-2">
<label>
<span>活动标题</span>
<input type="text" name="title" value="{{ activity.title }}" required />
</label>
<label>
<span>线索发布时间间隔(分钟)</span>
<input type="number" name="clue_interval_minutes" min="0" value="{{ activity.clue_interval_minutes if activity.clue_interval_minutes is not none else '' }}" placeholder="留空表示与活动开始同步" />
</label>
<label>
<span>开始时间</span>
<input type="datetime-local" name="start_at" value="{{ activity.start_at.strftime('%Y-%m-%dT%H:%M') }}" required />
</label>
<label>
<span>截止时间</span>
<input type="datetime-local" name="deadline_at" value="{{ activity.deadline_at.strftime('%Y-%m-%dT%H:%M') }}" required />
</label>
</div>
<label>
<span>活动说明</span>
<textarea name="description" rows="3">{{ activity.description or '' }}</textarea>
</label>
<label class="checkbox-row">
<input type="checkbox" name="is_visible" {% if activity.is_visible %}checked{% endif %} />
<span>允许用户查看该活动</span>
</label>
<label class="checkbox-row">
<input type="checkbox" name="leaderboard_visible" {% if activity.leaderboard_visible %}checked{% endif %} />
<span>允许用户查看实时排行榜</span>
</label>
</section>
<section class="glass-card leaderboard-card wide-panel">
<div class="section-head compact-head">
<div>
<p class="eyebrow">Live Ranking</p>
<h3>活动实时排行榜</h3>
<p class="mini-note">管理员始终可见,用于现场统筹和审核判断;用户是否可见由上方开关控制。</p>
</div>
<div class="chip-row">
<span class="chip">活动{{ '可见' if activity.is_visible else '隐藏' }}</span>
<span class="chip">排行榜{{ '可见' if activity.leaderboard_visible else '隐藏' }}</span>
</div>
</div>
<div class="rank-table-wrap">
<table class="rank-table">
<thead>
<tr>
<th>排名</th>
<th>小组</th>
<th>完成</th>
<th>人数</th>
<th>总耗时</th>
</tr>
</thead>
<tbody>
{% for row in leaderboard %}
<tr>
<td>#{{ loop.index }}</td>
<td>{{ row.group_name }}</td>
<td>{{ row.completed_count }}</td>
<td>{{ row.member_count }}</td>
<td>{{ row.total_elapsed|duration_human }}</td>
</tr>
{% else %}
<tr>
<td colspan="5">当前还没有通过审核的小组打卡记录。</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<section class="glass-card form-panel wide-panel">
<div class="section-head">
<div>
<p class="eyebrow">Existing Tasks</p>
<h3>已有任务</h3>
</div>
</div>
<div class="task-editor-list">
{% for task in activity.tasks %}
<article class="task-builder-card task-editor-card">
<div class="builder-title-row">
<strong>任务 {{ loop.index }}</strong>
<button
class="btn btn-danger small-btn"
type="submit"
form="delete-task-{{ task.id }}"
onclick="return confirm('确定删除这个任务吗?相关提交记录也会一并删除。');"
>删除任务</button>
</div>
<input type="hidden" name="existing_task_id" value="{{ task.id }}" />
<div class="form-grid cols-2">
<label>
<span>任务标题</span>
<input type="text" name="existing_task_title" value="{{ task.title }}" required />
</label>
<label>
<span>主图图床链接</span>
<input type="url" name="existing_task_image_url" value="{{ task.image_url or '' }}" placeholder="https://..." required />
</label>
<label class="full-span">
<span>任务描述</span>
<textarea name="existing_task_description" rows="2">{{ task.description or '' }}</textarea>
</label>
<label>
<span>线索图图床链接</span>
<input type="url" name="existing_task_clue_image_url" value="{{ task.clue_image_url or '' }}" placeholder="https://..." />
</label>
<label class="checkbox-row align-end-checkbox">
<input type="checkbox" name="existing_task_remove_clue" value="{{ task.id }}" />
<span>移除当前线索图</span>
</label>
</div>
<div class="editor-preview-grid">
<div>
<span class="mini-note">当前主图</span>
<img class="editor-thumb" src="{{ task.image_url if task.image_url else '/media/tasks/' ~ task.id ~ '/image' }}" alt="{{ task.title }} 主图" />
</div>
<div>
<span class="mini-note">当前线索图</span>
{% if task.clue_image_url or task.clue_image_filename or task.clue_image_path %}
<img class="editor-thumb" src="{{ task.clue_image_url if task.clue_image_url else '/media/tasks/' ~ task.id ~ '/clue' }}" alt="{{ task.title }} 线索图" />
{% else %}
<div class="empty-thumb">未设置线索图</div>
{% endif %}
</div>
</div>
<div class="chip-row">
<span class="chip">提交记录 {{ task.submissions|length }} 条</span>
<span class="chip">当前顺序 {{ task.display_order }}</span>
<span class="chip">线索发布时间 {{ task.clue_release_at|datetime_local if task.clue_release_at else '未发布' }}</span>
</div>
</article>
{% endfor %}
</div>
</section>
<section class="glass-card form-panel wide-panel">
<div class="section-head tight-head">
<div>
<p class="eyebrow">New Tasks</p>
<h3>新增任务</h3>
</div>
<button class="btn btn-secondary" type="button" id="add-new-task-btn">新增任务卡片</button>
</div>
<div class="task-builder" id="new-task-builder">
<article class="task-builder-card" data-new-task-template>
<div class="builder-title-row">
<strong>新增任务 1</strong>
<button type="button" class="btn btn-ghost small-btn" data-remove-new-task>删除</button>
</div>
<div class="form-grid cols-2">
<label>
<span>任务标题</span>
<input type="text" name="new_task_title" />
</label>
<label>
<span>主图图床链接</span>
<input type="url" name="new_task_image_url" placeholder="https://..." />
</label>
<label class="full-span">
<span>任务描述</span>
<textarea name="new_task_description" rows="2"></textarea>
</label>
<label class="full-span">
<span>线索图图床链接</span>
<input type="url" name="new_task_clue_image_url" placeholder="https://..." />
</label>
</div>
</article>
</div>
<div class="card-footer">
<span class="mini-note">保存后会自动重排任务顺序,并按新的顺序刷新线索发布时间。</span>
<button class="btn btn-primary" type="submit">保存活动修改</button>
</div>
</section>
</form>
<form id="delete-activity-form" method="post" action="/admin/activities/{{ activity.id }}/delete"></form>
{% for task in activity.tasks %}
<form id="delete-task-{{ task.id }}" method="post" action="/admin/tasks/{{ task.id }}/delete"></form>
{% endfor %}
<script>
(() => {
const builder = document.getElementById('new-task-builder');
const addBtn = document.getElementById('add-new-task-btn');
if (!builder || !addBtn) return;
const renumber = () => {
builder.querySelectorAll('[data-new-task-template]').forEach((card, index) => {
const title = card.querySelector('.builder-title-row strong');
if (title) title.textContent = `新增任务 ${index + 1}`;
});
};
const attachRemove = (card) => {
const removeBtn = card.querySelector('[data-remove-new-task]');
if (!removeBtn) return;
removeBtn.addEventListener('click', () => {
if (builder.querySelectorAll('[data-new-task-template]').length === 1) {
card.querySelectorAll('input, textarea').forEach((field) => {
field.value = '';
});
return;
}
card.remove();
renumber();
});
};
builder.querySelectorAll('[data-new-task-template]').forEach(attachRemove);
addBtn.addEventListener('click', () => {
const template = builder.querySelector('[data-new-task-template]');
const clone = template.cloneNode(true);
clone.querySelectorAll('input, textarea').forEach((field) => {
field.value = '';
});
attachRemove(clone);
builder.appendChild(clone);
renumber();
});
})();
</script>
{% endblock %}