This commit is contained in:
+81
-10
@@ -465,6 +465,30 @@ try {
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* ===== 操作反馈 loading ===== */
|
||||
/* 按钮内小号转圈 */
|
||||
.spinner.sm {
|
||||
width: 14px; height: 14px; margin: 0 7px -2px 0;
|
||||
border-width: 2px; display: inline-block;
|
||||
border-color: currentColor; border-top-color: transparent;
|
||||
}
|
||||
.btn.busy { opacity: .78; }
|
||||
/* 卡片勾选转圈:请求期间勾选框外套一圈 */
|
||||
.check.busy { position: relative; }
|
||||
.check.busy::after {
|
||||
content: ''; position: absolute; inset: -4px;
|
||||
border: 2px solid var(--indigo); border-top-color: transparent;
|
||||
border-radius: 50%; animation: spin .7s linear infinite;
|
||||
}
|
||||
/* 卡片删除中:半透明 + 右侧转圈 */
|
||||
.task.busy { opacity: .55; pointer-events: none; }
|
||||
.task.busy::after {
|
||||
content: ''; position: absolute; right: 16px; top: 50%; margin-top: -9px;
|
||||
width: 18px; height: 18px;
|
||||
border: 2px solid var(--indigo); border-top-color: transparent;
|
||||
border-radius: 50%; animation: spin .7s linear infinite;
|
||||
}
|
||||
|
||||
/* ===== 弹窗 ===== */
|
||||
.mask {
|
||||
position: fixed; inset: 0; z-index: 50; background: rgba(15, 23, 42, .42);
|
||||
@@ -1121,6 +1145,21 @@ try {
|
||||
}
|
||||
|
||||
/* ===== 请求 ===== */
|
||||
/* 按钮进入 loading 态:转圈 + 禁用,防重复提交;返回恢复函数。
|
||||
已处于 loading 时返回 null,调用方据此拦截本次点击 */
|
||||
function btnLoading(btn, text) {
|
||||
if (!btn || btn.disabled) return null
|
||||
var oldHtml = btn.innerHTML
|
||||
btn.disabled = true
|
||||
btn.classList.add('busy')
|
||||
btn.innerHTML = '<span class="spinner sm"></span>' + esc(text || '处理中')
|
||||
return function () {
|
||||
btn.disabled = false
|
||||
btn.classList.remove('busy')
|
||||
btn.innerHTML = oldHtml
|
||||
}
|
||||
}
|
||||
|
||||
function post(path, data) {
|
||||
var body = new URLSearchParams()
|
||||
Object.keys(data || {}).forEach(function (k) {
|
||||
@@ -1277,6 +1316,9 @@ try {
|
||||
function addTodo() {
|
||||
var title = (elQuick.value || '').trim()
|
||||
if (!title) { toast('先写点什么吧'); elQuick.focus(); return }
|
||||
// 添加按钮转圈,等添加请求与列表刷新完成后关闭动画
|
||||
var restore = btnLoading($('addBtn'), '添加中')
|
||||
if (!restore) return
|
||||
var data = {
|
||||
title: title,
|
||||
priority: getSel('quickPriority'),
|
||||
@@ -1288,17 +1330,25 @@ try {
|
||||
setDateSel($('quickDue'), '')
|
||||
toast('已添加')
|
||||
return fetchList(true).then(fetchStats)
|
||||
}).catch(function (e) { toast(e.message || '添加失败', true) })
|
||||
}).catch(function (e) {
|
||||
toast(e.message || '添加失败', true)
|
||||
}).then(function () { restore() })
|
||||
}
|
||||
|
||||
function toggleStatus(id, cur) {
|
||||
function toggleStatus(id, cur, el) {
|
||||
if (el && el.classList.contains('busy')) return
|
||||
if (el) el.classList.add('busy')
|
||||
post('/updateStatus', { id: id, status: cur === 1 ? 0 : 1 }).then(function (res) {
|
||||
if (!res || res.code !== 200) throw new Error((res && res.message) || '操作失败')
|
||||
return fetchList(true).then(fetchStats)
|
||||
}).catch(function (e) { toast(e.message || '操作失败', true) })
|
||||
}).catch(function (e) {
|
||||
toast(e.message || '操作失败', true)
|
||||
}).then(function () {
|
||||
if (el) el.classList.remove('busy')
|
||||
})
|
||||
}
|
||||
|
||||
function removeTodo(id, title) {
|
||||
function removeTodo(id, title, cardEl) {
|
||||
confirmDialog({
|
||||
title: '删除这条待办?',
|
||||
text: '删除后不再显示,且无法恢复。',
|
||||
@@ -1306,11 +1356,16 @@ try {
|
||||
okText: '删除'
|
||||
}).then(function (ok) {
|
||||
if (!ok) return
|
||||
// 确认后卡片进入删除中状态,等请求响应后由列表刷新移除
|
||||
if (cardEl) cardEl.classList.add('busy')
|
||||
return post('/remove', { id: id }).then(function (res) {
|
||||
if (!res || res.code !== 200) throw new Error((res && res.message) || '删除失败')
|
||||
toast('已删除')
|
||||
return fetchList(true).then(fetchStats)
|
||||
}).catch(function (e) { toast(e.message || '删除失败', true) })
|
||||
}).catch(function (e) {
|
||||
if (cardEl) cardEl.classList.remove('busy')
|
||||
toast(e.message || '删除失败', true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1376,6 +1431,9 @@ try {
|
||||
$('mSave').addEventListener('click', function () {
|
||||
var title = (mTitle.value || '').trim()
|
||||
if (!title) { toast('标题不能为空'); mTitle.focus(); return }
|
||||
// 保存按钮转圈,等请求响应成功后关闭弹窗与动画;失败恢复按钮可重试
|
||||
var restore = btnLoading(this, '保存中')
|
||||
if (!restore) return
|
||||
var remarkHtml = sanitizeHtml($('mRemark').innerHTML)
|
||||
if (!stripHtml(remarkHtml) && remarkHtml.indexOf('ck-box') === -1) remarkHtml = ''
|
||||
var data = {
|
||||
@@ -1398,12 +1456,18 @@ try {
|
||||
document.removeEventListener('keydown', onKey)
|
||||
toast(isNew ? '已添加' : '已保存')
|
||||
return fetchList(true).then(fetchStats)
|
||||
}).catch(function (e) { toast(e.message || '保存失败', true) })
|
||||
}).catch(function (e) {
|
||||
restore()
|
||||
toast(e.message || '保存失败', true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* ===== 导入导出 ===== */
|
||||
function exportJson() {
|
||||
// 导出按钮转圈,等数据拉取完成后再触发下载
|
||||
var restore = btnLoading($('exportBtn'), '导出中')
|
||||
if (!restore) return
|
||||
post('/list', { sort: 'created' }).then(function (res) {
|
||||
var list = (res && res.data) || []
|
||||
if (!list.length) { toast('没有可导出的数据'); return }
|
||||
@@ -1416,7 +1480,9 @@ try {
|
||||
document.body.removeChild(a)
|
||||
setTimeout(function () { URL.revokeObjectURL(a.href) }, 1000)
|
||||
toast('已导出 ' + list.length + ' 条')
|
||||
}).catch(function (e) { toast(e.message || '导出失败', true) })
|
||||
}).catch(function (e) {
|
||||
toast(e.message || '导出失败', true)
|
||||
}).then(function () { restore() })
|
||||
}
|
||||
|
||||
function clearDone() {
|
||||
@@ -1428,11 +1494,16 @@ try {
|
||||
okText: '清除'
|
||||
}).then(function (ok) {
|
||||
if (!ok) return
|
||||
// 确认后按钮转圈,等清理请求与列表刷新完成后关闭动画
|
||||
var restore = btnLoading($('clearDoneBtn'), '清理中')
|
||||
if (!restore) return
|
||||
return post('/removeDone', {}).then(function (res) {
|
||||
if (!res || res.code !== 200) throw new Error((res && res.message) || '清理失败')
|
||||
toast('已清除 ' + n + ' 条')
|
||||
return fetchList(true).then(fetchStats)
|
||||
}).catch(function (e) { toast(e.message || '清理失败', true) })
|
||||
}).catch(function (e) {
|
||||
toast(e.message || '清理失败', true)
|
||||
}).then(function () { restore() })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1525,9 +1596,9 @@ try {
|
||||
var act = actEl.dataset.act
|
||||
var item = state.items.filter(function (i) { return String(i.id) === String(id) })[0]
|
||||
if (!item) return
|
||||
if (act === 'toggle') toggleStatus(item.id, item.status)
|
||||
if (act === 'toggle') toggleStatus(item.id, item.status, actEl)
|
||||
else if (act === 'edit') openEdit(item)
|
||||
else if (act === 'del') removeTodo(item.id, item.title)
|
||||
else if (act === 'del') removeTodo(item.id, item.title, taskEl)
|
||||
else if (act === 'peek') {
|
||||
if (e.target.closest('a')) return // 点展开内容里的链接时不折叠
|
||||
actEl.classList.toggle('open')
|
||||
|
||||
Reference in New Issue
Block a user