This commit is contained in:
+81
-10
@@ -465,6 +465,30 @@ try {
|
|||||||
}
|
}
|
||||||
@keyframes spin { to { transform: rotate(360deg); } }
|
@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 {
|
.mask {
|
||||||
position: fixed; inset: 0; z-index: 50; background: rgba(15, 23, 42, .42);
|
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) {
|
function post(path, data) {
|
||||||
var body = new URLSearchParams()
|
var body = new URLSearchParams()
|
||||||
Object.keys(data || {}).forEach(function (k) {
|
Object.keys(data || {}).forEach(function (k) {
|
||||||
@@ -1277,6 +1316,9 @@ try {
|
|||||||
function addTodo() {
|
function addTodo() {
|
||||||
var title = (elQuick.value || '').trim()
|
var title = (elQuick.value || '').trim()
|
||||||
if (!title) { toast('先写点什么吧'); elQuick.focus(); return }
|
if (!title) { toast('先写点什么吧'); elQuick.focus(); return }
|
||||||
|
// 添加按钮转圈,等添加请求与列表刷新完成后关闭动画
|
||||||
|
var restore = btnLoading($('addBtn'), '添加中')
|
||||||
|
if (!restore) return
|
||||||
var data = {
|
var data = {
|
||||||
title: title,
|
title: title,
|
||||||
priority: getSel('quickPriority'),
|
priority: getSel('quickPriority'),
|
||||||
@@ -1288,17 +1330,25 @@ try {
|
|||||||
setDateSel($('quickDue'), '')
|
setDateSel($('quickDue'), '')
|
||||||
toast('已添加')
|
toast('已添加')
|
||||||
return fetchList(true).then(fetchStats)
|
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) {
|
post('/updateStatus', { id: id, status: cur === 1 ? 0 : 1 }).then(function (res) {
|
||||||
if (!res || res.code !== 200) throw new Error((res && res.message) || '操作失败')
|
if (!res || res.code !== 200) throw new Error((res && res.message) || '操作失败')
|
||||||
return fetchList(true).then(fetchStats)
|
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({
|
confirmDialog({
|
||||||
title: '删除这条待办?',
|
title: '删除这条待办?',
|
||||||
text: '删除后不再显示,且无法恢复。',
|
text: '删除后不再显示,且无法恢复。',
|
||||||
@@ -1306,11 +1356,16 @@ try {
|
|||||||
okText: '删除'
|
okText: '删除'
|
||||||
}).then(function (ok) {
|
}).then(function (ok) {
|
||||||
if (!ok) return
|
if (!ok) return
|
||||||
|
// 确认后卡片进入删除中状态,等请求响应后由列表刷新移除
|
||||||
|
if (cardEl) cardEl.classList.add('busy')
|
||||||
return post('/remove', { id: id }).then(function (res) {
|
return post('/remove', { id: id }).then(function (res) {
|
||||||
if (!res || res.code !== 200) throw new Error((res && res.message) || '删除失败')
|
if (!res || res.code !== 200) throw new Error((res && res.message) || '删除失败')
|
||||||
toast('已删除')
|
toast('已删除')
|
||||||
return fetchList(true).then(fetchStats)
|
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 () {
|
$('mSave').addEventListener('click', function () {
|
||||||
var title = (mTitle.value || '').trim()
|
var title = (mTitle.value || '').trim()
|
||||||
if (!title) { toast('标题不能为空'); mTitle.focus(); return }
|
if (!title) { toast('标题不能为空'); mTitle.focus(); return }
|
||||||
|
// 保存按钮转圈,等请求响应成功后关闭弹窗与动画;失败恢复按钮可重试
|
||||||
|
var restore = btnLoading(this, '保存中')
|
||||||
|
if (!restore) return
|
||||||
var remarkHtml = sanitizeHtml($('mRemark').innerHTML)
|
var remarkHtml = sanitizeHtml($('mRemark').innerHTML)
|
||||||
if (!stripHtml(remarkHtml) && remarkHtml.indexOf('ck-box') === -1) remarkHtml = ''
|
if (!stripHtml(remarkHtml) && remarkHtml.indexOf('ck-box') === -1) remarkHtml = ''
|
||||||
var data = {
|
var data = {
|
||||||
@@ -1398,12 +1456,18 @@ try {
|
|||||||
document.removeEventListener('keydown', onKey)
|
document.removeEventListener('keydown', onKey)
|
||||||
toast(isNew ? '已添加' : '已保存')
|
toast(isNew ? '已添加' : '已保存')
|
||||||
return fetchList(true).then(fetchStats)
|
return fetchList(true).then(fetchStats)
|
||||||
}).catch(function (e) { toast(e.message || '保存失败', true) })
|
}).catch(function (e) {
|
||||||
|
restore()
|
||||||
|
toast(e.message || '保存失败', true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 导入导出 ===== */
|
/* ===== 导入导出 ===== */
|
||||||
function exportJson() {
|
function exportJson() {
|
||||||
|
// 导出按钮转圈,等数据拉取完成后再触发下载
|
||||||
|
var restore = btnLoading($('exportBtn'), '导出中')
|
||||||
|
if (!restore) return
|
||||||
post('/list', { sort: 'created' }).then(function (res) {
|
post('/list', { sort: 'created' }).then(function (res) {
|
||||||
var list = (res && res.data) || []
|
var list = (res && res.data) || []
|
||||||
if (!list.length) { toast('没有可导出的数据'); return }
|
if (!list.length) { toast('没有可导出的数据'); return }
|
||||||
@@ -1416,7 +1480,9 @@ try {
|
|||||||
document.body.removeChild(a)
|
document.body.removeChild(a)
|
||||||
setTimeout(function () { URL.revokeObjectURL(a.href) }, 1000)
|
setTimeout(function () { URL.revokeObjectURL(a.href) }, 1000)
|
||||||
toast('已导出 ' + list.length + ' 条')
|
toast('已导出 ' + list.length + ' 条')
|
||||||
}).catch(function (e) { toast(e.message || '导出失败', true) })
|
}).catch(function (e) {
|
||||||
|
toast(e.message || '导出失败', true)
|
||||||
|
}).then(function () { restore() })
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearDone() {
|
function clearDone() {
|
||||||
@@ -1428,11 +1494,16 @@ try {
|
|||||||
okText: '清除'
|
okText: '清除'
|
||||||
}).then(function (ok) {
|
}).then(function (ok) {
|
||||||
if (!ok) return
|
if (!ok) return
|
||||||
|
// 确认后按钮转圈,等清理请求与列表刷新完成后关闭动画
|
||||||
|
var restore = btnLoading($('clearDoneBtn'), '清理中')
|
||||||
|
if (!restore) return
|
||||||
return post('/removeDone', {}).then(function (res) {
|
return post('/removeDone', {}).then(function (res) {
|
||||||
if (!res || res.code !== 200) throw new Error((res && res.message) || '清理失败')
|
if (!res || res.code !== 200) throw new Error((res && res.message) || '清理失败')
|
||||||
toast('已清除 ' + n + ' 条')
|
toast('已清除 ' + n + ' 条')
|
||||||
return fetchList(true).then(fetchStats)
|
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 act = actEl.dataset.act
|
||||||
var item = state.items.filter(function (i) { return String(i.id) === String(id) })[0]
|
var item = state.items.filter(function (i) { return String(i.id) === String(id) })[0]
|
||||||
if (!item) return
|
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 === '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') {
|
else if (act === 'peek') {
|
||||||
if (e.target.closest('a')) return // 点展开内容里的链接时不折叠
|
if (e.target.closest('a')) return // 点展开内容里的链接时不折叠
|
||||||
actEl.classList.toggle('open')
|
actEl.classList.toggle('open')
|
||||||
|
|||||||
Reference in New Issue
Block a user