File size: 8,949 Bytes
ce0719e 3d6b7f2 ce0719e 4fb3744 ce0719e 3d6b7f2 ce0719e 3d6b7f2 ce0719e cfce07c ce0719e 26d04d9 ce0719e cfce07c ce0719e cfce07c 36b4039 cfce07c 36b4039 cfce07c 36b4039 cfce07c 36b4039 4fb3744 3d06737 cfce07c 36b4039 cfce07c 4fb3744 36b4039 cfce07c 26d04d9 cfce07c 3d06737 36b4039 cfce07c 36b4039 cfce07c 36b4039 ce0719e cfce07c 36b4039 cfce07c ce0719e 4fb3744 ce0719e 26d04d9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | {% 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 %}
|