fix(public-todo): 弹窗三段式布局修复滚动与圆角,拆分两级放大
CI/CD Deploy (Web) / 构建前端镜像并部署 (push) Successful in 26s

- 弹窗改为 flex 三段式:头部固定 / 内容滚动 / 底栏固定
  保存按钮常驻底部可见,外层 overflow:hidden 保住 20px 圆角
- 滚动条收进 modal-body,细圆角样式,不再贯穿整个弹窗
- 放大拆两级:弹窗头部 ⤢ 放大整个弹窗(加宽到 880px);
  编辑器工具栏 ⤢ 仅编辑器最大化(980px x 92vh 浮层)
- Esc 优先还原编辑器最大化,再按一次才关闭弹窗
This commit is contained in:
张家训
2026-10-20 09:24:34 +08:00
parent 39bef6a68b
commit 0aa7d3a293
+55 -12
View File
@@ -201,9 +201,16 @@
} }
.rte-body:empty::before { content: attr(data-ph); color: var(--text-muted); pointer-events: none; } .rte-body:empty::before { content: attr(data-ph); color: var(--text-muted); pointer-events: none; }
/* 放大书写面板:编辑弹窗右上角按钮切换 */ /* 弹窗级放大:弹窗头部 ⤢ 按钮切换,加宽整个弹窗 */
.modal.lg { max-width: 880px; } .modal.lg { max-width: 880px; }
.modal.lg .rte-body { min-height: 48vh; max-height: none; } /* 编辑器级最大化:工具栏 ⤢ 按钮切换,编辑器浮层铺满视口 */
.rte.max {
position: fixed; z-index: 80; top: 4vh; left: 50%; transform: translateX(-50%);
width: min(980px, 94vw); height: 92vh; display: flex; flex-direction: column;
background: #fff; border-color: transparent; border-radius: 16px;
box-shadow: 0 24px 64px rgba(15, 23, 42, .4);
}
.rte.max .rte-body { flex: 1; min-height: 0; max-height: none; padding: 18px 22px; font-size: 15px; }
/* 富文本排版(编辑器与卡片展开共用 .fmt) */ /* 富文本排版(编辑器与卡片展开共用 .fmt) */
.fmt h2 { font-size: 17px; font-weight: 700; margin: 10px 0 5px; letter-spacing: -.2px; } .fmt h2 { font-size: 17px; font-weight: 700; margin: 10px 0 5px; letter-spacing: -.2px; }
@@ -344,16 +351,31 @@
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.modal { .modal {
width: 100%; max-width: 480px; background: #fff; border-radius: 20px; width: 100%; max-width: 480px; background: #fff; border-radius: 20px;
box-shadow: var(--shadow-lg); padding: 24px; margin: auto; box-shadow: var(--shadow-lg); margin: auto; padding: 0;
animation: popIn .26s cubic-bezier(.16, 1, .3, 1); max-height: 92vh; overflow-y: auto; animation: popIn .26s cubic-bezier(.16, 1, .3, 1);
max-height: 88vh; display: flex; flex-direction: column; overflow: hidden;
} }
@keyframes popIn { from { opacity: 0; transform: scale(.94) translateY(12px); } to { opacity: 1; transform: scale(1) translateY(0); } } @keyframes popIn { from { opacity: 0; transform: scale(.94) translateY(12px); } to { opacity: 1; transform: scale(1) translateY(0); } }
.modal h2 { font-size: 18px; font-weight: 700; margin-bottom: 18px; } /* 三段式布局:头部固定 / 内容滚动 / 底栏固定,外层 overflow:hidden 保住圆角 */
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 20px 24px 14px; flex-shrink: 0; }
.modal-head h2 { font-size: 18px; font-weight: 700; margin: 0; }
.modal-body { padding: 2px 24px 6px; overflow-y: auto; flex: 1; min-height: 0; }
.modal-body::-webkit-scrollbar, .rte-body::-webkit-scrollbar { width: 6px; }
.modal-body::-webkit-scrollbar-thumb, .rte-body::-webkit-scrollbar-thumb { background: #d5dbea; border-radius: 99px; }
.modal-body::-webkit-scrollbar-thumb:hover, .rte-body::-webkit-scrollbar-thumb:hover { background: #b6c0d8; }
.modal-body::-webkit-scrollbar-track, .rte-body::-webkit-scrollbar-track { background: transparent; }
.modal-foot { display: flex; gap: 10px; justify-content: flex-end; padding: 14px 24px; flex-shrink: 0; border-top: 1px solid var(--line); background: #fff; }
.mzoom {
width: 30px; height: 30px; border: none; background: #f1f4fa; border-radius: 8px;
color: var(--text-sub); cursor: pointer; font-size: 14px; flex-shrink: 0;
display: inline-flex; align-items: center; justify-content: center;
transition: background .14s, color .14s;
}
.mzoom:hover { background: rgba(99, 102, 241, .13); color: var(--indigo-dark); }
.field { margin-bottom: 15px; } .field { margin-bottom: 15px; }
.field label { display: block; font-size: 13px; font-weight: 600; color: var(--text-sub); margin-bottom: 7px; } .field label { display: block; font-size: 13px; font-weight: 600; color: var(--text-sub); margin-bottom: 7px; }
.field textarea { resize: vertical; min-height: 76px; } .field textarea { resize: vertical; min-height: 76px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.modal-foot { display: flex; gap: 10px; justify-content: flex-end; margin-top: 22px; }
/* ===== 危险操作确认弹窗(替代浏览器原生 confirm) ===== */ /* ===== 危险操作确认弹窗(替代浏览器原生 confirm) ===== */
.confirm { max-width: 372px; padding: 26px 24px 20px; text-align: center; } .confirm { max-width: 372px; padding: 26px 24px 20px; text-align: center; }
@@ -905,10 +927,9 @@
var btn = e.target.closest('.rte-btn') var btn = e.target.closest('.rte-btn')
if (!btn) return if (!btn) return
if (btn.id === 'rteExpand') { if (btn.id === 'rteExpand') {
var modal = root.closest('.modal') var maxed = root.classList.toggle('max')
var lg = modal.classList.toggle('lg') btn.innerHTML = maxed ? '⤡ 还原' : '⤢ 放大'
btn.innerHTML = lg ? '⤡ 收起' : '⤢ 放大' btn.title = maxed ? '还原编辑器' : '编辑器最大化'
btn.title = lg ? '恢复默认面板' : '放大书写面板'
body.focus() body.focus()
return return
} }
@@ -1176,7 +1197,9 @@
var isNew = !item var isNew = !item
item = item || { priority: 2, dueDate: '', remark: '', title: '' } item = item || { priority: 2, dueDate: '', remark: '', title: '' }
var html = '<div class="mask" id="mask"><div class="modal">' var html = '<div class="mask" id="mask"><div class="modal">'
+ '<h2>' + (isNew ? '新建待办' : '编辑待办') + '</h2>' + '<div class="modal-head"><h2>' + (isNew ? '新建待办' : '编辑待办') + '</h2>'
+ '<button type="button" class="mzoom" id="mZoom" title="放大弹窗">⤢</button></div>'
+ '<div class="modal-body">'
+ '<div class="field"><label>标题</label>' + '<div class="field"><label>标题</label>'
+ '<input type="text" id="mTitle" maxlength="200" value="' + esc(item.title) + '" placeholder="要做什么?"></div>' + '<input type="text" id="mTitle" maxlength="200" value="' + esc(item.title) + '" placeholder="要做什么?"></div>'
+ '<div class="field-row">' + '<div class="field-row">'
@@ -1187,6 +1210,7 @@
+ '<div class="rte">' + RTE_BAR + '<div class="rte">' + RTE_BAR
+ '<div class="rte-body fmt" id="mRemark" contenteditable="true" data-ph="补充说明,支持加粗 / 列表 / 待办项 / 代码块…"></div>' + '<div class="rte-body fmt" id="mRemark" contenteditable="true" data-ph="补充说明,支持加粗 / 列表 / 待办项 / 代码块…"></div>'
+ '</div></div>' + '</div></div>'
+ '</div>'
+ '<div class="modal-foot">' + '<div class="modal-foot">'
+ '<button class="btn btn-ghost" id="mCancel">取消</button>' + '<button class="btn btn-ghost" id="mCancel">取消</button>'
+ '<button class="btn btn-primary" id="mSave">保存</button>' + '<button class="btn btn-primary" id="mSave">保存</button>'
@@ -1199,11 +1223,30 @@
rteBody.innerHTML = sanitizeHtml(item.remark || '') rteBody.innerHTML = sanitizeHtml(item.remark || '')
initEditor(rteBody.closest('.rte')) initEditor(rteBody.closest('.rte'))
var mask = $('mask') var mask = $('mask')
var modal = mask.querySelector('.modal')
var mTitle = $('mTitle') var mTitle = $('mTitle')
setTimeout(function () { mTitle.focus() }, 60) setTimeout(function () { mTitle.focus() }, 60)
// 弹窗头部 ⤢:放大整个弹窗(加宽)
$('mZoom').addEventListener('click', function () {
var lg = modal.classList.toggle('lg')
this.innerHTML = lg ? '⤡' : '⤢'
this.title = lg ? '还原弹窗' : '放大弹窗'
})
function close() { elModalRoot.innerHTML = '' } function close() { elModalRoot.innerHTML = '' }
function onKey(e) { if (e.key === 'Escape') close() } function onKey(e) {
if (e.key !== 'Escape') return
// 编辑器最大化时 Esc 优先还原编辑器,再按一次才关弹窗
var maxed = modal.querySelector('.rte.max')
if (maxed) {
maxed.classList.remove('max')
var expBtn = maxed.querySelector('#rteExpand')
if (expBtn) { expBtn.innerHTML = '⤢ 放大'; expBtn.title = '编辑器最大化' }
return
}
close()
}
document.addEventListener('keydown', onKey) document.addEventListener('keydown', onKey)
mask.addEventListener('click', function (e) { if (e.target === mask) { close(); document.removeEventListener('keydown', onKey) } }) mask.addEventListener('click', function (e) { if (e.target === mask) { close(); document.removeEventListener('keydown', onKey) } })