摘要
把 Day 16 的「點擊把任務卡放進時段格」升級為「拖曳(Drag & Drop)」,並在放下(drop)後同步寫入 localStorage。同時保留點擊作為鍵盤與行動裝置的回退方案(a11y & fallback)。
<button id="morningSlot" class="time-slot" type="button" data-period="morning" aria-dropeffect="move" aria-label="早上格子(☀️)">☀️ 早上</button>
<button id="afternoonSlot" class="time-slot" type="button" data-period="afternoon" aria-dropeffect="move" aria-label="下午格子(🧠)">🧠 下午</button>
<button id="eveningSlot" class="time-slot" type="button" data-period="evening" aria-dropeffect="move" aria-label="晚上格子(🌃)">🌃 晚上</button>
// [Day17-NEW] 任務卡可被拖曳
card.setAttribute('draggable', 'true');
card.addEventListener('dragstart', (e) => {
// 以目前任務 id 當作拖曳資料(文字即可)
e.dataTransfer.setData('text/plain', currentTaskId || '');
// 可選:提供移動效果
e.dataTransfer.effectAllowed = 'move';
});
// [Day17-NEW] 允許把卡片拖進三個時段格子
[morningSlotEl, afternoonSlotEl, eveningSlotEl].forEach(slot => {
if (!slot) return;
// 關鍵:dragover 必須 preventDefault,否則 drop 不會觸發
slot.addEventListener('dragover', (e) => {
e.preventDefault(); // ★★ 必要點!否則無法觸發 drop
e.dataTransfer.dropEffect = 'move';
slot.classList.add('dropping'); // 可選:加上暫時樣式(若有 CSS)
});
slot.addEventListener('dragleave', () => {
slot.classList.remove('dropping');
});
slot.addEventListener('drop', (e) => {
e.preventDefault();
slot.classList.remove('dropping');
// 從拖曳資料取回 recordId(此範例實際用不到,示範完整流程)
const draggedId = e.dataTransfer.getData('text/plain');
// 用 data-period 取得目標時段,沿用 Day16 的存檔流程
const period = slot.dataset.period;
if (period) {
placeTaskTo(period); // 內部已 appendChild + writeScheduleMap
// a11y 文字回饋
scheduleFeedback.textContent = `已安排到:${PERIOD_LABEL[period] || period}`;
}
});
});
// [Day17-NEW] 顯示時再保險地掛上 drag 事件(renderTaskCard 也會做一次)
const cardEl = document.getElementById('taskCard');
if (cardEl && !cardEl.hasAttribute('draggable')) {
cardEl.setAttribute('draggable', 'true');
cardEl.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', currentTaskId || '');
e.dataTransfer.effectAllowed = 'move';
});
}
保留 Day 16 的點擊綁定(鍵盤操作/行動裝置回退),不需要刪除:
// 仍保留點擊版(a11y 與觸控回退)
morningSlotEl?.addEventListener('click', () => placeTaskTo('morning'));
afternoonSlotEl?.addEventListener('click', () => placeTaskTo('afternoon'));
eveningSlotEl?.addEventListener('click', () => placeTaskTo('evening'));
在原生 Drag & Drop 中,瀏覽器預設不允許在某元素上放下拖曳資料。
只有在 dragover 事件中呼叫 e.preventDefault(),瀏覽器才會把該元素視為「可放置目標」,進而觸發後續的 drop。少了這一步,drop 完全不會被觸發。