cam / app /templates /admin_activities.html
cacode's picture
Upload 60 files
26d04d9 verified
{% extends 'base.html' %}
{% block content %}
<section class="page-grid admin-page-grid admin-activity-grid">
<article class="glass-card form-panel wide-panel">
<div class="section-head">
<div>
<p class="eyebrow">Create Activity</p>
<h3>发布活动与打卡任务</h3>
</div>
</div>
<form method="post" action="/admin/activities" class="form-stack" id="activity-form">
<div class="form-grid cols-2">
<label>
<span>活动标题</span>
<input type="text" name="title" required />
</label>
<label>
<span>线索发布时间间隔(分钟)</span>
<input type="number" name="clue_interval_minutes" min="0" placeholder="留空表示与活动开始同步" />
</label>
<label>
<span>开始时间</span>
<input type="datetime-local" name="start_at" required />
</label>
<label>
<span>截止时间</span>
<input type="datetime-local" name="deadline_at" required />
</label>
</div>
<label>
<span>活动说明</span>
<textarea name="description" rows="3" placeholder="介绍活动安排、打卡要求和注意事项"></textarea>
</label>
<label class="checkbox-row">
<input type="checkbox" name="is_visible" checked />
<span>允许用户查看该活动</span>
</label>
<label class="checkbox-row">
<input type="checkbox" name="leaderboard_visible" checked />
<span>允许用户查看实时排行榜</span>
</label>
<div class="section-head tight-head">
<div>
<p class="eyebrow">Tasks</p>
<h3>任务卡片</h3>
</div>
<button class="btn btn-secondary" type="button" id="add-task-btn">新增任务卡片</button>
</div>
<div class="task-builder" id="task-builder">
<article class="task-builder-card" data-task-template>
<div class="builder-title-row">
<strong>任务 1</strong>
<button type="button" class="btn btn-ghost small-btn" data-remove-task>删除</button>
</div>
<div class="form-grid cols-2">
<label>
<span>任务标题</span>
<input type="text" name="task_title" required />
</label>
<label>
<span>主图图床链接</span>
<input type="url" name="task_image_url" placeholder="https://..." required />
</label>
<label class="full-span">
<span>任务描述</span>
<textarea name="task_description" rows="2"></textarea>
</label>
<label class="full-span">
<span>线索图图床链接</span>
<input type="url" name="task_clue_image_url" placeholder="https://..." />
</label>
</div>
</article>
</div>
<button class="btn btn-primary" type="submit">发布活动</button>
</form>
</article>
<article class="glass-card table-panel activity-catalog-panel">
<div class="section-head activity-catalog-head">
<div>
<p class="eyebrow">Published Activities</p>
<h3>已发布活动</h3>
<p class="mini-note"></p>
</div>
</div>
<div class="published-activity-list">
{% for activity in activities %}
<article class="published-activity-card">
<div class="published-activity-hero">
<div class="published-activity-title-wrap">
<p class="eyebrow">Activity {{ loop.index }}</p>
<h3 class="published-activity-title">{{ activity.title }}</h3>
</div>
<div class="published-activity-badges">
<span class="status-badge {% if activity.is_visible %}status-approved{% else %}status-rejected{% endif %}">
{{ '活动可见' if activity.is_visible else '活动隐藏' }}
</span>
<span class="status-badge {% if activity.leaderboard_visible %}status-approved{% else %}status-rejected{% endif %}">
{{ '排行可见' if activity.leaderboard_visible else '排行隐藏' }}
</span>
</div>
</div>
<div class="published-activity-metrics">
<div class="activity-metric-card">
<span>开始时间</span>
<strong>{{ activity.start_at|datetime_local }}</strong>
</div>
<div class="activity-metric-card">
<span>截止时间</span>
<strong>{{ activity.deadline_at|datetime_local }}</strong>
</div>
<div class="activity-metric-card">
<span>任务数量</span>
<strong>{{ activity.tasks|length }} 个任务</strong>
</div>
<div class="activity-metric-card">
<span>创建人</span>
<strong>{{ activity.created_by.display_name }}</strong>
</div>
<div class="activity-metric-card">
<span>线索间隔</span>
<strong>{{ activity.clue_interval_minutes if activity.clue_interval_minutes is not none else '与活动开始同步' }}</strong>
</div>
</div>
<form method="post" action="/admin/activities/{{ activity.id }}/visibility" class="published-settings-form">
<div class="published-toggle-grid">
<label class="published-toggle-card">
<div class="published-toggle-copy">
<strong>活动对用户可见</strong>
<span>关闭后,用户端不会显示,也不能直接进入此活动。</span>
</div>
<input type="checkbox" name="is_visible" {% if activity.is_visible %}checked{% endif %} />
</label>
<label class="published-toggle-card">
<div class="published-toggle-copy">
<strong>排行榜对用户可见</strong>
<span>管理员始终可见,只控制普通用户是否能看到排行榜。</span>
</div>
<input type="checkbox" name="leaderboard_visible" {% if activity.leaderboard_visible %}checked{% endif %} />
</label>
</div>
<div class="published-action-row">
<a class="btn btn-primary" href="/admin/activities/{{ activity.id }}/edit">编辑活动</a>
<button class="btn btn-secondary" type="submit">保存显示设置</button>
</div>
</form>
<form method="post" action="/admin/activities/{{ activity.id }}/delete" class="published-delete-form">
<button class="btn btn-danger" type="submit" onclick="return confirm('确定删除这个活动吗?相关任务、审核记录和本地提交图片都会一并删除。');">删除活动</button>
</form>
</article>
{% else %}
<article class="empty-state">
<h3>还没有活动</h3>
<p>先在上方发布一个活动,这里会自动整理成清晰的管理卡片。</p>
</article>
{% endfor %}
</div>
</article>
</section>
<script>
(() => {
const builder = document.getElementById('task-builder');
const addBtn = document.getElementById('add-task-btn');
if (!builder || !addBtn) return;
const renumber = () => {
builder.querySelectorAll('[data-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-task]');
if (!removeBtn) return;
removeBtn.addEventListener('click', () => {
if (builder.querySelectorAll('[data-task-template]').length === 1) return;
card.remove();
renumber();
});
};
builder.querySelectorAll('[data-task-template]').forEach(attachRemove);
addBtn.addEventListener('click', () => {
const template = builder.querySelector('[data-task-template]');
const clone = template.cloneNode(true);
clone.querySelectorAll('input, textarea').forEach((field) => {
if (field.type === 'checkbox') {
field.checked = false;
} else {
field.value = '';
}
});
attachRemove(clone);
builder.appendChild(clone);
renumber();
});
})();
</script>
{% endblock %}