接口跑通,基础功能全部实现
This commit is contained in:
@@ -1,30 +1,365 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Add Task - Weibo-HotSign{% endblock %}
|
||||
{% 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 %}
|
||||
|
||||
{% block content %}
|
||||
<div style="max-width: 600px; margin: 0 auto;">
|
||||
<h1 style="margin-bottom: 30px;">Add Signin Task</h1>
|
||||
<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>
|
||||
|
||||
<div class="card">
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label for="cron_expression">Cron Expression</label>
|
||||
<input type="text" id="cron_expression" name="cron_expression" required placeholder="e.g., 0 9 * * *">
|
||||
<small style="color: #999; display: block; margin-top: 8px;">
|
||||
Standard cron format (minute hour day month weekday)<br>
|
||||
Examples:<br>
|
||||
• <code>0 9 * * *</code> - Every day at 9:00 AM<br>
|
||||
• <code>0 9 * * 1-5</code> - Weekdays at 9:00 AM<br>
|
||||
• <code>0 9,21 * * *</code> - Every day at 9:00 AM and 9:00 PM
|
||||
</small>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 重复模式 -->
|
||||
<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 * * *">
|
||||
<div class="btn-group">
|
||||
<button type="submit" class="btn btn-primary">Create Task</button>
|
||||
<a href="{{ url_for('account_detail', account_id=account_id) }}" class="btn btn-secondary">Cancel</a>
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user