File size: 11,216 Bytes
3d06737
 
 
 
 
 
 
 
3d6b7f2
3d06737
 
 
 
 
4fb3744
 
 
 
3d06737
 
 
3d6b7f2
3d06737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4fb3744
 
 
 
3d06737
 
 
 
 
 
02a8414
 
 
 
 
 
 
 
4fb3744
 
02a8414
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d06737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d6b7f2
 
3d06737
 
 
 
 
 
3d6b7f2
 
3d06737
 
 
 
 
 
 
 
 
 
3d6b7f2
3d06737
 
 
3d6b7f2
 
3d06737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d6b7f2
 
3d06737
 
 
 
 
 
3d6b7f2
 
3d06737
 
 
 
 
 
 
 
 
 
 
 
4fb3744
 
3d06737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
{% 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 %}