2026-03-09 16:10:29 +08:00
|
|
|
{% extends "base.html" %}
|
|
|
|
|
|
2026-03-16 16:14:08 +08:00
|
|
|
{% block title %}添加任务 - 微博超话签到{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block extra_css %}
|
|
|
|
|
<style>
|
|
|
|
|
.task-container { max-width: 560px; margin: 0 auto; }
|
|
|
|
|
|
|
|
|
|
/* 时间轮盘 */
|
|
|
|
|
.wheel-section {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
margin: 24px 0 28px;
|
|
|
|
|
}
|
|
|
|
|
.wheel-col { text-align: center; }
|
|
|
|
|
.wheel-col .wheel-label {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
}
|
|
|
|
|
.wheel-wrapper {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 90px;
|
|
|
|
|
height: 180px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
border: 1.5px solid #e2e8f0;
|
|
|
|
|
}
|
|
|
|
|
.wheel-wrapper::before, .wheel-wrapper::after {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 0; right: 0;
|
|
|
|
|
height: 60px;
|
|
|
|
|
z-index: 2;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
.wheel-wrapper::before {
|
|
|
|
|
top: 0;
|
|
|
|
|
background: linear-gradient(to bottom, rgba(248,250,252,1), rgba(248,250,252,0));
|
|
|
|
|
}
|
|
|
|
|
.wheel-wrapper::after {
|
|
|
|
|
bottom: 0;
|
|
|
|
|
background: linear-gradient(to top, rgba(248,250,252,1), rgba(248,250,252,0));
|
|
|
|
|
}
|
|
|
|
|
.wheel-highlight {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 50%; left: 50%;
|
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
width: 78px; height: 44px;
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
background: linear-gradient(135deg, rgba(99,102,241,0.1), rgba(168,85,247,0.1));
|
|
|
|
|
border: 2px solid rgba(99,102,241,0.3);
|
|
|
|
|
z-index: 1;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
.wheel-scroll {
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
height: 100%;
|
|
|
|
|
overflow-y: scroll;
|
|
|
|
|
scroll-snap-type: y mandatory;
|
|
|
|
|
-ms-overflow-style: none;
|
|
|
|
|
scrollbar-width: none;
|
|
|
|
|
}
|
|
|
|
|
.wheel-scroll::-webkit-scrollbar { display: none; }
|
|
|
|
|
.wheel-item {
|
|
|
|
|
height: 44px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #cbd5e1;
|
|
|
|
|
scroll-snap-align: center;
|
|
|
|
|
transition: color 0.15s, transform 0.15s;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
user-select: none;
|
|
|
|
|
}
|
|
|
|
|
.wheel-item.active {
|
|
|
|
|
color: #6366f1;
|
|
|
|
|
transform: scale(1.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 重复模式 */
|
|
|
|
|
.repeat-options {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
}
|
|
|
|
|
.repeat-chip {
|
|
|
|
|
padding: 8px 18px;
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
border: 1.5px solid #e2e8f0;
|
|
|
|
|
background: #fff;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #64748b;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
user-select: none;
|
|
|
|
|
}
|
|
|
|
|
.repeat-chip:hover { border-color: #818cf8; color: #6366f1; }
|
|
|
|
|
.repeat-chip.selected {
|
|
|
|
|
background: linear-gradient(135deg, #6366f1, #818cf8);
|
|
|
|
|
color: white;
|
|
|
|
|
border-color: transparent;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(99,102,241,0.25);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 星期选择 */
|
|
|
|
|
.weekday-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
}
|
|
|
|
|
.weekday-btn {
|
|
|
|
|
width: 44px; height: 44px;
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
border: 1.5px solid #e2e8f0;
|
|
|
|
|
background: #fff;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #64748b;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
.weekday-btn:hover { border-color: #818cf8; }
|
|
|
|
|
.weekday-btn.selected {
|
|
|
|
|
background: linear-gradient(135deg, #6366f1, #818cf8);
|
|
|
|
|
color: white;
|
|
|
|
|
border-color: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 预览 */
|
|
|
|
|
.cron-preview {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
padding: 16px 20px;
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
border: 1.5px solid #e2e8f0;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
.cron-preview code {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
background: #eef2ff;
|
|
|
|
|
color: #6366f1;
|
|
|
|
|
padding: 3px 10px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
font-family: 'SF Mono', Monaco, monospace;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
.cron-preview .desc { color: #64748b; margin-top: 6px; }
|
|
|
|
|
</style>
|
|
|
|
|
{% endblock %}
|
2026-03-09 16:10:29 +08:00
|
|
|
|
|
|
|
|
{% block content %}
|
2026-03-16 16:14:08 +08:00
|
|
|
<div class="task-container">
|
|
|
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:24px;">
|
|
|
|
|
<h1 style="font-size:24px; font-weight:700; color:#1e293b;">⏰ 添加签到任务</h1>
|
|
|
|
|
<a href="{{ url_for('account_detail', account_id=account_id) }}" class="btn btn-secondary">返回</a>
|
|
|
|
|
</div>
|
2026-03-09 16:10:29 +08:00
|
|
|
|
|
|
|
|
<div class="card">
|
2026-03-16 16:14:08 +08:00
|
|
|
<div class="card-header">选择签到时间</div>
|
|
|
|
|
|
|
|
|
|
<!-- 时间轮盘 -->
|
|
|
|
|
<div class="wheel-section">
|
|
|
|
|
<div class="wheel-col">
|
|
|
|
|
<div class="wheel-label">时</div>
|
|
|
|
|
<div class="wheel-wrapper">
|
|
|
|
|
<div class="wheel-highlight"></div>
|
|
|
|
|
<div class="wheel-scroll" id="hour-wheel"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div style="font-size:32px; font-weight:700; color:#cbd5e1; padding-top:50px;">:</div>
|
|
|
|
|
<div class="wheel-col">
|
|
|
|
|
<div class="wheel-label">分</div>
|
|
|
|
|
<div class="wheel-wrapper">
|
|
|
|
|
<div class="wheel-highlight"></div>
|
|
|
|
|
<div class="wheel-scroll" id="minute-wheel"></div>
|
|
|
|
|
</div>
|
2026-03-09 16:10:29 +08:00
|
|
|
</div>
|
2026-03-16 16:14:08 +08:00
|
|
|
</div>
|
2026-03-09 16:10:29 +08:00
|
|
|
|
2026-03-16 16:14:08 +08:00
|
|
|
<!-- 重复模式 -->
|
|
|
|
|
<div class="card-header" style="margin-top:8px;">重复方式</div>
|
|
|
|
|
<div class="repeat-options">
|
|
|
|
|
<div class="repeat-chip selected" data-mode="daily" onclick="setRepeat('daily')">每天</div>
|
|
|
|
|
<div class="repeat-chip" data-mode="weekdays" onclick="setRepeat('weekdays')">工作日</div>
|
|
|
|
|
<div class="repeat-chip" data-mode="custom" onclick="setRepeat('custom')">自定义</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="weekday-picker" style="display:none; margin-top:12px;">
|
|
|
|
|
<div class="weekday-row">
|
|
|
|
|
<div class="weekday-btn" data-day="1" onclick="toggleDay(this)">一</div>
|
|
|
|
|
<div class="weekday-btn" data-day="2" onclick="toggleDay(this)">二</div>
|
|
|
|
|
<div class="weekday-btn" data-day="3" onclick="toggleDay(this)">三</div>
|
|
|
|
|
<div class="weekday-btn" data-day="4" onclick="toggleDay(this)">四</div>
|
|
|
|
|
<div class="weekday-btn" data-day="5" onclick="toggleDay(this)">五</div>
|
|
|
|
|
<div class="weekday-btn" data-day="6" onclick="toggleDay(this)">六</div>
|
|
|
|
|
<div class="weekday-btn" data-day="0" onclick="toggleDay(this)">日</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Cron 预览 -->
|
|
|
|
|
<div class="cron-preview">
|
|
|
|
|
<div>生成的 Cron 表达式: <code id="cron-display">0 8 * * *</code></div>
|
|
|
|
|
<div class="desc" id="cron-desc">每天 08:00 执行签到</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 提交 -->
|
|
|
|
|
<form method="POST" id="task-form" style="margin-top:20px;">
|
|
|
|
|
<input type="hidden" id="cron_expression" name="cron_expression" value="0 8 * * *">
|
2026-03-09 16:10:29 +08:00
|
|
|
<div class="btn-group">
|
2026-03-16 16:14:08 +08:00
|
|
|
<button type="submit" class="btn btn-primary" style="flex:1; text-align:center;">创建任务</button>
|
|
|
|
|
<a href="{{ url_for('account_detail', account_id=account_id) }}" class="btn btn-secondary" style="text-align:center;">取消</a>
|
2026-03-09 16:10:29 +08:00
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-16 16:14:08 +08:00
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
let selectedHour = 8;
|
|
|
|
|
let selectedMinute = 0;
|
|
|
|
|
let repeatMode = 'daily';
|
|
|
|
|
let customDays = new Set();
|
|
|
|
|
|
|
|
|
|
// 初始化轮盘
|
|
|
|
|
function initWheel(containerId, count, padFn, onSelect) {
|
|
|
|
|
const container = document.getElementById(containerId);
|
|
|
|
|
// 添加前后空白占位(让第一个和最后一个能滚到中间)
|
|
|
|
|
const padCount = 2;
|
|
|
|
|
for (let i = 0; i < padCount; i++) {
|
|
|
|
|
const spacer = document.createElement('div');
|
|
|
|
|
spacer.className = 'wheel-item';
|
|
|
|
|
spacer.style.visibility = 'hidden';
|
|
|
|
|
container.appendChild(spacer);
|
|
|
|
|
}
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
|
const item = document.createElement('div');
|
|
|
|
|
item.className = 'wheel-item';
|
|
|
|
|
item.textContent = padFn(i);
|
|
|
|
|
item.dataset.value = i;
|
|
|
|
|
item.addEventListener('click', () => {
|
|
|
|
|
item.scrollIntoView({behavior:'smooth', block:'center'});
|
|
|
|
|
});
|
|
|
|
|
container.appendChild(item);
|
|
|
|
|
}
|
|
|
|
|
for (let i = 0; i < padCount; i++) {
|
|
|
|
|
const spacer = document.createElement('div');
|
|
|
|
|
spacer.className = 'wheel-item';
|
|
|
|
|
spacer.style.visibility = 'hidden';
|
|
|
|
|
container.appendChild(spacer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 滚动监听
|
|
|
|
|
let scrollTimer;
|
|
|
|
|
container.addEventListener('scroll', () => {
|
|
|
|
|
clearTimeout(scrollTimer);
|
|
|
|
|
scrollTimer = setTimeout(() => {
|
|
|
|
|
const items = container.querySelectorAll('.wheel-item:not([style*="hidden"])');
|
|
|
|
|
const center = container.scrollTop + container.clientHeight / 2;
|
|
|
|
|
let closest = null, minDist = Infinity;
|
|
|
|
|
items.forEach(el => {
|
|
|
|
|
const dist = Math.abs(el.offsetTop + 22 - center);
|
|
|
|
|
if (dist < minDist) { minDist = dist; closest = el; }
|
|
|
|
|
});
|
|
|
|
|
if (closest && closest.dataset.value !== undefined) {
|
|
|
|
|
items.forEach(el => el.classList.remove('active'));
|
|
|
|
|
closest.classList.add('active');
|
|
|
|
|
onSelect(parseInt(closest.dataset.value));
|
|
|
|
|
}
|
|
|
|
|
}, 60);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return container;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pad2(n) { return n.toString().padStart(2, '0'); }
|
|
|
|
|
|
|
|
|
|
const hourWheel = initWheel('hour-wheel', 24, pad2, v => { selectedHour = v; updateCron(); });
|
|
|
|
|
const minuteWheel = initWheel('minute-wheel', 60, pad2, v => { selectedMinute = v; updateCron(); });
|
|
|
|
|
|
|
|
|
|
// 初始滚动到默认值
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
scrollToValue(hourWheel, selectedHour);
|
|
|
|
|
scrollToValue(minuteWheel, selectedMinute);
|
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
|
|
function scrollToValue(container, value) {
|
|
|
|
|
const items = container.querySelectorAll('.wheel-item');
|
|
|
|
|
items.forEach(el => {
|
|
|
|
|
if (el.dataset.value == value) {
|
|
|
|
|
el.scrollIntoView({block:'center'});
|
|
|
|
|
el.classList.add('active');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setRepeat(mode) {
|
|
|
|
|
repeatMode = mode;
|
|
|
|
|
document.querySelectorAll('.repeat-chip').forEach(c => c.classList.remove('selected'));
|
|
|
|
|
document.querySelector(`.repeat-chip[data-mode="${mode}"]`).classList.add('selected');
|
|
|
|
|
document.getElementById('weekday-picker').style.display = mode === 'custom' ? 'block' : 'none';
|
|
|
|
|
updateCron();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggleDay(el) {
|
|
|
|
|
const day = el.dataset.day;
|
|
|
|
|
if (customDays.has(day)) {
|
|
|
|
|
customDays.delete(day);
|
|
|
|
|
el.classList.remove('selected');
|
|
|
|
|
} else {
|
|
|
|
|
customDays.add(day);
|
|
|
|
|
el.classList.add('selected');
|
|
|
|
|
}
|
|
|
|
|
updateCron();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dayNames = ['日','一','二','三','四','五','六'];
|
|
|
|
|
|
|
|
|
|
function updateCron() {
|
|
|
|
|
let dow = '*';
|
|
|
|
|
let desc = '';
|
|
|
|
|
|
|
|
|
|
if (repeatMode === 'daily') {
|
|
|
|
|
dow = '*';
|
|
|
|
|
desc = '每天';
|
|
|
|
|
} else if (repeatMode === 'weekdays') {
|
|
|
|
|
dow = '1-5';
|
|
|
|
|
desc = '工作日';
|
|
|
|
|
} else {
|
|
|
|
|
if (customDays.size === 0) {
|
|
|
|
|
dow = '*';
|
|
|
|
|
desc = '每天';
|
|
|
|
|
} else if (customDays.size === 7) {
|
|
|
|
|
dow = '*';
|
|
|
|
|
desc = '每天';
|
|
|
|
|
} else {
|
|
|
|
|
const sorted = [...customDays].sort((a,b) => a - b);
|
|
|
|
|
dow = sorted.join(',');
|
|
|
|
|
desc = '每周' + sorted.map(d => dayNames[d]).join('、');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cron = `${selectedMinute} ${selectedHour} * * ${dow}`;
|
|
|
|
|
const timeStr = pad2(selectedHour) + ':' + pad2(selectedMinute);
|
|
|
|
|
|
|
|
|
|
document.getElementById('cron-display').textContent = cron;
|
|
|
|
|
document.getElementById('cron-desc').textContent = `${desc} ${timeStr} 执行签到`;
|
|
|
|
|
document.getElementById('cron_expression').value = cron;
|
|
|
|
|
}
|
|
|
|
|
</script>
|
2026-03-09 16:10:29 +08:00
|
|
|
{% endblock %}
|