diff --git a/app.py b/app.py index 7150caa..98d6769 100644 --- a/app.py +++ b/app.py @@ -193,9 +193,9 @@ def api_create_wish(): name = body.get('name', '').strip() if not name: return jsonify({'ok': False, 'error': '名称不能为空'}), 400 - priority = body.get('priority', '中') + quadrant = body.get('quadrant', '重要不紧急') deadline = body.get('deadline', '') - wid = save_wish(name, priority, deadline) + wid = save_wish(name, quadrant, deadline) return jsonify({'ok': True, 'id': wid}) diff --git a/database.py b/database.py index 7cbe37f..2a27d77 100644 --- a/database.py +++ b/database.py @@ -11,7 +11,7 @@ os.makedirs(DB_DIR, exist_ok=True) DB_PATH = os.path.join(DB_DIR, 'ziwei_power.db') # 当前数据库 schema 版本 —— 改表结构时必须 +1 并补迁移逻辑 -CURRENT_SCHEMA_VERSION = 2 +CURRENT_SCHEMA_VERSION = 3 def get_conn(): @@ -69,6 +69,11 @@ def init_db(): ) ''') + if current < 3: + # v3: 优先级改为四象限 + conn.execute("ALTER TABLE wishes ADD COLUMN quadrant TEXT NOT NULL DEFAULT '重要不紧急'") + conn.execute("UPDATE wishes SET quadrant = CASE priority WHEN '高' THEN '重要紧急' WHEN '中' THEN '重要不紧急' WHEN '低' THEN '不紧急不重要' ELSE '重要不紧急' END WHERE quadrant = '重要不紧急'") + # ── 将来加字段/改表在此扩展 ── # if current < 2: # conn.execute('ALTER TABLE checkins ADD COLUMN tags TEXT DEFAULT ""') @@ -152,14 +157,14 @@ def get_wishes(): return [dict(row) for row in rows] -def save_wish(name, priority, deadline): +def save_wish(name, quadrant, deadline): """新增一条心愿""" now = datetime.now().isoformat() conn = get_conn() max_order = conn.execute('SELECT COALESCE(MAX(sort_order), -1) + 1 AS n FROM wishes').fetchone()['n'] conn.execute( - 'INSERT INTO wishes (name, priority, deadline, done, sort_order, created_at) VALUES (?, ?, ?, 0, ?, ?)', - (name, priority, deadline, max_order, now) + 'INSERT INTO wishes (name, quadrant, deadline, done, sort_order, created_at) VALUES (?, ?, ?, 0, ?, ?)', + (name, quadrant, deadline, max_order, now) ) conn.commit() wish_id = conn.execute('SELECT last_insert_rowid()').fetchone()[0] @@ -169,7 +174,7 @@ def save_wish(name, priority, deadline): def update_wish(wish_id, **kwargs): """更新心愿字段""" - allowed = ['name', 'priority', 'deadline', 'done'] + allowed = ['name', 'quadrant', 'deadline', 'done'] updates = {k: v for k, v in kwargs.items() if k in allowed} if not updates: return diff --git a/static/app.js b/static/app.js index cadd7ee..1c76612 100644 --- a/static/app.js +++ b/static/app.js @@ -636,15 +636,16 @@ }; /* ================================================================ - Wishes — 心愿清单 + Wishes — 心愿清单(四象限) ================================================================ */ var wishes = []; var dragSourceId = null; + var dragSourceQuadrant = null; var wishesLoaded = false; + var QUADRANTS = ['重要紧急', '重要不紧急', '紧急不重要', '不紧急不重要']; function loadWishes() { if (wishesLoaded) { renderWishes(); return; } - // 优先使用页面嵌入数据 if (window.__INITIAL_WISHES__) { wishes = window.__INITIAL_WISHES__; wishesLoaded = true; @@ -657,49 +658,54 @@ } function renderWishes() { - var list = document.getElementById('wishes-list'); - var empty = document.getElementById('wishes-empty'); - if (!list) return; - if (wishes.length === 0) { - list.innerHTML = ''; - if (empty) empty.style.display = 'block'; + var emptyEl = document.getElementById('wishes-empty'); + if (!wishes.length) { + QUADRANTS.forEach(function(q){ document.getElementById('quad-list-' + q).innerHTML = ''; }); + if (emptyEl) emptyEl.style.display = 'block'; return; } - if (empty) empty.style.display = 'none'; - var html = ''; - for (var i = 0; i < wishes.length; i++) { - var w = wishes[i]; - var doneCls = w.done ? ' done' : ''; - var priCls = 'pri-' + (w.priority || '中'); - var deadline = w.deadline ? w.deadline : ''; - html += '
' + - '' + - '' + - '' + esc(w.name) + '' + - '' + esc(w.priority) + '' + - (deadline ? '' + esc(deadline) + '' : '') + - '' + - '
'; - } - list.innerHTML = html; + if (emptyEl) emptyEl.style.display = 'none'; + + // 按象限分组渲染 + QUADRANTS.forEach(function(quadrant) { + var list = document.getElementById('quad-list-' + quadrant); + if (!list) return; + var items = wishes.filter(function(w){ return (w.quadrant || w.priority || '重要不紧急') === quadrant; }); + var html = ''; + for (var i = 0; i < items.length; i++) { + var w = items[i]; + var doneCls = w.done ? ' done' : ''; + var deadline = w.deadline ? w.deadline : ''; + html += '
' + + '' + + '' + + '' + esc(w.name) + '' + + (deadline ? '' + esc(deadline) + '' : '') + + '' + + '
'; + } + list.innerHTML = html; + }); + bindDragEvents(); } - /* ── Drag & Drop ── */ + /* ── Drag & Drop (跨象限) ── */ function bindDragEvents() { - var items = document.querySelectorAll('#wishes-list .wish-item'); + var items = document.querySelectorAll('#panel-wishes .wish-item'); items.forEach(function(item) { item.addEventListener('dragstart', function(e) { dragSourceId = parseInt(this.dataset.id); + dragSourceQuadrant = this.dataset.quadrant; this.classList.add('dragging'); e.dataTransfer.effectAllowed = 'move'; }); - item.addEventListener('dragend', function(e) { + item.addEventListener('dragend', function() { this.classList.remove('dragging'); dragSourceId = null; - // 移除所有 over 状态 - document.querySelectorAll('#wishes-list .wish-item').forEach(function(el){ el.classList.remove('drag-over'); }); + dragSourceQuadrant = null; + document.querySelectorAll('.quad-cell, .wish-item').forEach(function(el){ el.classList.remove('drag-over'); }); }); item.addEventListener('dragover', function(e) { e.preventDefault(); @@ -711,23 +717,60 @@ }); item.addEventListener('drop', function(e) { e.preventDefault(); + e.stopPropagation(); this.classList.remove('drag-over'); var targetId = parseInt(this.dataset.id); + var targetQuadrant = this.dataset.quadrant; if (dragSourceId === targetId) return; - // 更新本地顺序 + var fromIdx = -1, toIdx = -1; for (var j = 0; j < wishes.length; j++) { if (wishes[j].id === dragSourceId) fromIdx = j; if (wishes[j].id === targetId) toIdx = j; } if (fromIdx >= 0 && toIdx >= 0) { - var item = wishes.splice(fromIdx, 1)[0]; - wishes.splice(toIdx, 0, item); + var moved = wishes.splice(fromIdx, 1)[0]; + wishes.splice(toIdx, 0, moved); + // 更新象限 + if (dragSourceQuadrant !== targetQuadrant) { + moved.quadrant = targetQuadrant; + fetch('/api/wishes/' + moved.id, { + method: 'PUT', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({quadrant: targetQuadrant}) + }); + } renderWishes(); saveWishOrder(); } }); }); + + // 象限空区域接受 drop + document.querySelectorAll('.quad-cell').forEach(function(cell) { + cell.addEventListener('dragover', function(e) { + if (this.querySelector('.wish-item')) return; // 有内容时让 item 处理 + e.preventDefault(); + this.classList.add('drag-over'); + }); + cell.addEventListener('dragleave', function() { this.classList.remove('drag-over'); }); + cell.addEventListener('drop', function(e) { + this.classList.remove('drag-over'); + if (this.querySelector('.wish-item')) return; // 已由 item 处理 + e.preventDefault(); + var q = this.dataset.quadrant; + var moved = wishes.find(function(w){ return w.id === dragSourceId; }); + if (moved) { + moved.quadrant = q; + fetch('/api/wishes/' + moved.id, { + method: 'PUT', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({quadrant: q}) + }); + renderWishes(); + } + }); + }); } function saveWishOrder() { @@ -751,23 +794,24 @@ if (form) form.style.display = 'none'; document.getElementById('wish-name').value = ''; document.getElementById('wish-deadline').value = ''; - document.getElementById('wish-priority').value = '中'; + var sel = document.getElementById('wish-quadrant'); + if (sel) sel.value = '重要不紧急'; }; window.addWish = function() { var name = document.getElementById('wish-name').value.trim(); if (!name) { showToast('请输入心愿名称', 'error'); return; } - var priority = document.getElementById('wish-priority').value; + var quadrant = document.getElementById('wish-quadrant').value; var deadline = document.getElementById('wish-deadline').value; fetch('/api/wishes', { method: 'POST', headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({name: name, priority: priority, deadline: deadline}) + body: JSON.stringify({name: name, quadrant: quadrant, deadline: deadline}) }).then(function(r){ return r.json(); }) .then(function(res){ if (!res.ok) { showToast(res.error, 'error'); return; } hideWishForm(); - wishes.push({id: res.id, name: name, priority: priority, deadline: deadline, done: 0}); + wishes.push({id: res.id, name: name, quadrant: quadrant, deadline: deadline, done: 0}); renderWishes(); }); }; diff --git a/static/style.css b/static/style.css index 9a530e9..d8d6d89 100644 --- a/static/style.css +++ b/static/style.css @@ -1081,7 +1081,7 @@ body { .toast.info { background: var(--primary); color: #FFF; } /* ═══════════════════════════════════════════ - Wishes Panel + Wishes — 四象限 ═══════════════════════════════════════════ */ #panel-wishes.editing .edit-only { display: inline-flex; } @@ -1108,135 +1108,116 @@ body { box-sizing: border-box; background: var(--card); } -.wish-form input[type="text"]:focus, -.wish-form input[type="date"]:focus, -.wish-form select:focus { +.wish-form input:focus, .wish-form select:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(74,108,247,0.08); } -.wish-form-row { - display: flex; - gap: 8px; -} -.wish-form-row select, -.wish-form-row input { flex: 1; } -.wish-form-actions { - display: flex; - gap: 8px; -} +.wish-form-row { display: flex; gap: 8px; } +.wish-form-row select, .wish-form-row input { flex: 1; } +.wish-form-actions { display: flex; gap: 8px; } .btn-wish-save { - flex: 1; - padding: 8px; - border: none; - background: var(--primary); - color: #FFF; - border-radius: var(--radius-sm); - font-size: 13px; - font-weight: 500; - cursor: pointer; - font-family: inherit; + flex: 1; padding: 8px; border: none; background: var(--primary); color: #FFF; + border-radius: var(--radius-sm); font-size: 13px; font-weight: 500; + cursor: pointer; font-family: inherit; } .btn-wish-save:hover { background: var(--primary-dark); } .btn-wish-cancel { - flex: 1; - padding: 8px; - border: 1.5px solid var(--border); + flex: 1; padding: 8px; border: 1.5px solid var(--border); background: var(--card); + color: var(--text-dim); border-radius: var(--radius-sm); font-size: 13px; + cursor: pointer; font-family: inherit; +} + +/* 四象限网格 */ +.quad-grid { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + gap: 12px; + min-height: 400px; +} + +.quad-cell { background: var(--card); - color: var(--text-dim); - border-radius: var(--radius-sm); + border: 1.5px solid var(--border); + border-radius: var(--radius); + padding: 12px 14px; + display: flex; + flex-direction: column; + min-height: 180px; + transition: border-color 0.2s, box-shadow 0.2s; +} +.quad-cell.drag-over { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(74,108,247,0.10); + background: var(--primary-light); +} + +.quad-title { font-size: 13px; - cursor: pointer; - font-family: inherit; + font-weight: 600; + color: var(--text); + margin-bottom: 10px; + padding-bottom: 8px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; } -.wishes-grid { - max-width: 640px; -} - -.wishes-list { +.quad-list { + flex: 1; display: flex; flex-direction: column; gap: 4px; -} - -.wishes-empty { - font-size: 13px; - color: var(--text-muted); - text-align: center; - padding: 32px 0; + min-height: 40px; } .wish-item { display: flex; align-items: center; - gap: 10px; - padding: 10px 12px; + gap: 8px; + padding: 7px 10px; border-radius: var(--radius-sm); - background: var(--card); - border: 0.5px solid var(--border); - box-shadow: var(--shadow); - transition: background 0.15s, box-shadow 0.15s; + background: var(--bg); + transition: opacity 0.15s, box-shadow 0.15s; } -.wish-item:hover { box-shadow: var(--shadow-hover); } -.wish-item.dragging { opacity: 0.4; } +.wish-item:hover { box-shadow: 0 1px 3px rgba(0,0,0,0.06); } +.wish-item.dragging { opacity: 0.3; } .wish-item.drag-over { - background: var(--primary-light); box-shadow: inset 0 0 0 2px var(--primary); + background: var(--card); } .wish-drag-handle { - color: var(--text-muted); - cursor: grab; - flex-shrink: 0; - display: flex; - align-items: center; + color: var(--text-muted); cursor: grab; flex-shrink: 0; + display: flex; align-items: center; } .wish-drag-handle:active { cursor: grabbing; } .wish-check { - width: 16px; - height: 16px; - accent-color: var(--success); - flex-shrink: 0; - cursor: pointer; + width: 15px; height: 15px; accent-color: var(--success); + flex-shrink: 0; cursor: pointer; } .wish-name { - flex: 1; - font-size: 14px; - color: var(--text); - min-width: 0; - word-break: break-all; + flex: 1; font-size: 13px; color: var(--text); min-width: 0; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.wish-item.done .wish-name { - text-decoration: line-through; - color: var(--text-muted); -} - -.wish-pri { - font-size: 11px; - font-weight: 600; - padding: 2px 8px; - border-radius: 10px; - white-space: nowrap; - flex-shrink: 0; -} -.wish-pri.pri-高 { background: var(--danger-light); color: var(--danger); } -.wish-pri.pri-中 { background: var(--warning-light); color: #D97706; } -.wish-pri.pri-低 { background: var(--bg); color: var(--text-muted); } +.wish-item.done .wish-name { text-decoration: line-through; color: var(--text-muted); } .wish-deadline { - font-size: 12px; - color: var(--text-dim); - white-space: nowrap; - flex-shrink: 0; + font-size: 11px; color: var(--text-dim); white-space: nowrap; flex-shrink: 0; } -.wish-del { - display: none; +.wish-del { display: none; } +.wish-del .icon-xs { width: 13px; height: 13px; } + +.wishes-empty { + font-size: 13px; color: var(--text-muted); text-align: center; padding: 32px 0; +} + +@media (max-width: 900px) { + .quad-grid { grid-template-columns: 1fr; grid-template-rows: auto; } } -.wish-del .icon-xs { width: 14px; height: 14px; } /* ═══════════════════════════════════════════ SVG icons helpers diff --git a/templates/index.html b/templates/index.html index a50775f..e8864e3 100644 --- a/templates/index.html +++ b/templates/index.html @@ -205,10 +205,11 @@ - - -
-
-
暂无心愿,点击上方按钮添加
+ +
+
+
重要 & 紧急
+
+
+
+
重要 & 不紧急
+
+
+
+
紧急 & 不重要
+
+
+
+
不紧急 & 不重要
+
+
+