diff --git a/app.py b/app.py index 83be079..9d319f8 100644 --- a/app.py +++ b/app.py @@ -308,6 +308,23 @@ def api_calendar_sync(): return jsonify({'ok': True, 'data': events}) +@app.route('/api/calendar-sync-all', methods=['GET']) +@login_required +def api_calendar_sync_all(): + """批量查询过去15天~未来15天的钉钉日程,按日期分组返回""" + import json as _json + from datetime import datetime, timedelta + today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + results = [] + for delta in range(-15, 16): + d = today + timedelta(days=delta) + ds = d.strftime('%Y-%m-%d') + events = _fetch_dingtalk_events(ds) + if events: + results.append({'date': ds, 'events': events}) + return jsonify({'ok': True, 'data': results}) + + # ── 启动 ────────────────────────────────────────────── if __name__ == '__main__': diff --git a/static/app.js b/static/app.js index 1d953a1..8e5afc2 100644 --- a/static/app.js +++ b/static/app.js @@ -365,42 +365,94 @@ /* ── 日历同步 ── */ window.syncCalendar = function() { - var date = document.getElementById('check-date').value; + var overlay = document.getElementById('sync-modal-overlay'); + var loading = document.getElementById('sync-loading'); + var resultsEl = document.getElementById('sync-results'); + var summary = document.getElementById('sync-summary'); + var list = document.getElementById('sync-list'); var btn = document.querySelector('.btn-cal-sync'); + if (!overlay) return; + + // 打开弹窗,显示加载中 + overlay.style.display = 'flex'; + loading.style.display = 'block'; + resultsEl.style.display = 'none'; if (btn) { btn.style.opacity = '0.5'; btn.disabled = true; } - fetch('/api/calendar-sync?date=' + encodeURIComponent(date)) + + fetch('/api/calendar-sync-all') .then(function(r){ return r.json(); }) .then(function(res){ + loading.style.display = 'none'; + resultsEl.style.display = 'block'; if (btn) { btn.style.opacity = '1'; btn.disabled = false; } - if (!res.ok) { showToast(res.error || '同步失败', 'error'); return; } - var events = res.data || []; - if (events.length === 0) { showToast('今日无日程', 'info'); return; } - var added = 0; - events.forEach(function(e) { - var text = '【' + e.time + '】' + e.summary; - if (e.location) text += ' @' + e.location; - var existing = document.querySelectorAll('#morning-list input[type="text"]'); - var already = false; - existing.forEach(function(inp){ if (inp.value.indexOf(e.summary) >= 0) already = true; }); - if (already) return; - var count = document.querySelectorAll('#morning-list .item-row').length; - if (count >= 3) return; - addMorningRow(text); - added++; - }); - if (added > 0) { - showToast('已同步 ' + added + ' 条日程', 'info'); - triggerAutoSave(); - } else { - showToast('日程已存在或已满', 'info'); + if (!res.ok) { summary.innerHTML = '同步失败: ' + (res.error || '未知错误') + ''; return; } + var data = res.data || []; + if (data.length === 0) { + summary.innerHTML = '未查询到任何日程'; + return; } + // 统计 + var totalDays = data.length; + var totalEvents = 0; + data.forEach(function(d){ totalEvents += d.events.length; }); + summary.innerHTML = '同步完成!共 ' + totalDays + ' 天有日程,合计 ' + totalEvents + ' 个会议'; + + // 渲染结果列表 + var html = ''; + data.forEach(function(day) { + html += '
'; + html += '
' + day.date + '
'; + day.events.forEach(function(e) { + var timeStr = e.time ? ' (' + e.time + ')' : ''; + html += '
' + + '📌' + + '' + esc(e.summary) + '' + esc(timeStr) + '' + + '
'; + }); + html += '
'; + }); + list.innerHTML = html; + + // 自动填充今天到选中日期的日程 + autoFillTodayEvents(data); }) .catch(function(){ + loading.style.display = 'none'; + resultsEl.style.display = 'block'; + summary.innerHTML = '网络错误,请重试'; if (btn) { btn.style.opacity = '1'; btn.disabled = false; } - showToast('同步失败', 'error'); }); }; + function autoFillTodayEvents(data) { + var todayDate = document.getElementById('check-date').value; + var added = 0; + data.forEach(function(day) { + if (day.date !== todayDate) return; + day.events.forEach(function(e) { + var text = e.time ? '【' + e.time + '】' + e.summary : e.summary; + if (e.location) text += ' @' + e.location; + var existing = document.querySelectorAll('#morning-list input[type="text"]'); + var already = false; + existing.forEach(function(inp){ if (inp.value.indexOf(e.summary) >= 0) already = true; }); + if (already) return; + var count = document.querySelectorAll('#morning-list .item-row').length; + if (count >= 3) return; + addMorningRow(text); + added++; + }); + }); + if (added > 0) { + showToast('已自动填充 ' + added + ' 条今日日程', 'info'); + triggerAutoSave(); + } + } + + window.closeSyncModal = function() { + var overlay = document.getElementById('sync-modal-overlay'); + if (overlay) overlay.style.display = 'none'; + }; + window.addMorning = function() { var count = document.querySelectorAll('#morning-list .item-row').length; if (count >= 3) { showToast('最多 3 条立志', 'error'); return; } diff --git a/static/style.css b/static/style.css index d6a4849..ae75243 100644 --- a/static/style.css +++ b/static/style.css @@ -1081,6 +1081,124 @@ body { .toast.error { background: var(--danger); color: #FFF; } .toast.info { background: var(--primary); color: #FFF; } +/* ═══════════════════════════════════════════ + Sync Modal + ═══════════════════════════════════════════ */ + +.sync-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.4); + display: flex; + align-items: center; + justify-content: center; + z-index: 9998; + backdrop-filter: blur(2px); +} +.sync-modal { + background: var(--card); + border-radius: var(--radius); + width: 90%; + max-width: 560px; + max-height: 80vh; + display: flex; + flex-direction: column; + box-shadow: 0 8px 40px rgba(0,0,0,0.15); + overflow: hidden; +} +.sync-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.sync-modal-header h3 { + font-size: 16px; + font-weight: 700; + margin: 0; +} +.sync-modal-close { + background: none; + border: none; + font-size: 22px; + color: var(--text-muted); + cursor: pointer; + padding: 0 4px; + line-height: 1; +} +.sync-modal-loading { + padding: 40px 20px; + text-align: center; + color: var(--text-dim); + font-size: 14px; +} +.sync-spinner { + width: 36px; + height: 36px; + border: 3px solid var(--border); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin: 0 auto 14px; +} +@keyframes spin { to { transform: rotate(360deg); } } +.sync-modal-results { + padding: 16px 20px; + overflow-y: auto; + flex: 1; +} +.sync-summary { + font-size: 14px; + color: var(--text); + margin-bottom: 14px; + padding: 10px 14px; + background: var(--primary-light); + border-radius: var(--radius-sm); +} +.sync-list { + display: flex; + flex-direction: column; + gap: 10px; +} +.sync-day { + border: 1px solid var(--border); + border-radius: var(--radius-sm); + overflow: hidden; +} +.sync-day-header { + background: var(--bg); + padding: 6px 12px; + font-size: 12px; + font-weight: 600; + color: var(--text-dim); + border-bottom: 1px solid var(--border); +} +.sync-event { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + font-size: 13px; + color: var(--text); +} +.sync-event + .sync-event { border-top: 1px solid var(--bg); } +.sync-event-icon { flex-shrink: 0; } +.sync-event-text { flex: 1; } +.sync-event-time { font-size: 11px; color: var(--text-muted); margin-left: 6px; } +.sync-modal-footer { + padding: 12px 20px; + border-top: 1px solid var(--border); + display: flex; + justify-content: flex-end; + flex-shrink: 0; +} +.sync-modal-footer .btn-wish-save { + width: auto; + padding: 8px 24px; +} + /* ═══════════════════════════════════════════ Wishes — 四象限 ═══════════════════════════════════════════ */ diff --git a/templates/index.html b/templates/index.html index 900ca4c..6556a4b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -13,6 +13,27 @@
+ + +