fix: WebUI 应用变更确认框改用 FlyonUI overlay 组件

原实现混用了 DaisyUI 的写法(<dialog class="modal"> + .modal-box),
而项目实际引入的是 FlyonUI:其 modal 由 HSOverlay 驱动,显示态靠
overlay-open 变体(&.open)生成,且该变体不会随 @plugin 自动注册
(Tailwind v4 只加载包内同名 flyonui.css,variants.css 被忽略)。

后果是 showModal() 只产生原生遮罩(页面变暗+其余按钮被锁),
对话框本身始终 opacity:0 不可见,也未发出任何请求。

本次改动:
- modal 结构迁移为官方 overlay 结构,开合改用 HSOverlay.open/close,
  取消/关闭按钮走 data-overlay 声明式关闭;
- input.css 注册 overlay-open 变体(带注释说明不可删);
- 顺带修正其余 DaisyUI 残留:tabs-border→tabs-bordered、
  btn-ghost/badge-ghost/badge-neutral→btn-soft/badge-soft。
This commit is contained in:
2026-09-30 00:41:03 +08:00
parent 6d31cba631
commit 2ddef46a55
6 changed files with 103 additions and 55 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+17 -12
View File
@@ -31,7 +31,7 @@ const STATE_BADGES = {
leave_candidate: ['待发离职确认', 'badge-warning'],
pending_confirm: ['待人工确认离职', 'badge-warning'],
confirmed_leave: ['已确认离职', 'badge-error'],
deleted: ['已删除', 'badge-neutral'],
deleted: ['已删除', 'badge-soft'],
};
// ---------------------------------------------------------------- 主题
@@ -246,7 +246,7 @@ function renderUsers() {
});
$('#userRows').innerHTML = rows.map((u) => {
const st = STATE_BADGES[u.state] || [u.state || '未知', 'badge-neutral'];
const st = STATE_BADGES[u.state] || [u.state || '未知', 'badge-soft'];
return `<tr>
<td class="font-medium">${escapeHtml(u.uid)}</td>
<td>${escapeHtml(u.name || '')}</td>
@@ -337,7 +337,7 @@ function renderPlan(plan) {
const ops = plan.ops || [];
$('#planRows').innerHTML = ops.map((op) => `
<tr>
<td><span class="badge ${OP_BADGES[op.kind] || 'badge-neutral'} badge-sm">${OP_LABELS[op.kind] || op.kind}</span></td>
<td><span class="badge ${OP_BADGES[op.kind] || 'badge-soft'} badge-sm">${OP_LABELS[op.kind] || op.kind}</span></td>
<td class="font-medium">${escapeHtml(op.target)}</td>
<td>${escapeHtml(op.detail)}</td>
</tr>`).join('');
@@ -345,9 +345,18 @@ function renderPlan(plan) {
$('#planEmpty').classList.toggle('hidden', ops.length > 0);
}
// FlyonUI 的 overlay(modal)由 flyonui.js 的 HSOverlay 驱动:它切换 open/hidden class 来显隐。
// 不要改回原生 dialog.showModal()——那不会加上 .open,overlay-open 变体不生效,框就永远不可见。
function openApplyModal() {
if (window.HSOverlay) window.HSOverlay.open('#applyModal');
}
function closeApplyModal() {
if (window.HSOverlay) window.HSOverlay.close('#applyModal');
}
async function applyPlan(force = false) {
const modal = document.getElementById('applyModal');
if (modal && typeof modal.close === 'function') modal.close();
closeApplyModal();
try {
const d = await api('/api/sync', {
method: 'POST',
@@ -372,7 +381,7 @@ async function loadRuns() {
<tr>
<td class="whitespace-nowrap">${fmtTime(r.startedAt)}</td>
<td>${r.mode === 'dry-run'
? '<span class="badge badge-ghost badge-sm">预览</span>'
? '<span class="badge badge-soft badge-sm">预览</span>'
: '<span class="badge badge-primary badge-sm">执行</span>'}</td>
<td>${r.trigger === 'schedule' ? '定时' : '手动'}</td>
<td>${r.status === 'success'
@@ -443,14 +452,10 @@ document.addEventListener('DOMContentLoaded', () => {
<div>删除用户 <strong>${s.deleteUser || 0}</strong></div>
<div>新建分组 <strong>${s.createGroup || 0}</strong> · 加入分组 <strong>${s.addMember || 0}</strong> · 移出分组 <strong>${s.removeMember || 0}</strong></div>
</div>`;
const modal = document.getElementById('applyModal');
if (modal && typeof modal.showModal === 'function') modal.showModal();
openApplyModal();
});
$('#applyCancel').addEventListener('click', () => {
const modal = document.getElementById('applyModal');
if (modal && typeof modal.close === 'function') modal.close();
});
// 「取消」与右上角关闭按钮走 data-overlay 声明式关闭,无需额外监听。
$('#applyConfirm').addEventListener('click', () => applyPlan(false));
// 已有会话则直接进入主界面
+27 -15
View File
@@ -40,14 +40,14 @@
<span class="hidden sm:inline text-sm text-base-content/50">飞书通讯录 → LLDAP</span>
</div>
<div class="flex-none flex items-center gap-2">
<button id="themeToggle" class="btn btn-sm btn-ghost" title="切换明暗主题">主题</button>
<button id="themeToggle" class="btn btn-sm btn-soft" title="切换明暗主题">主题</button>
<button id="logoutBtn" class="btn btn-sm btn-outline">退出</button>
</div>
</header>
<main class="max-w-7xl mx-auto p-4">
<!-- 页签 -->
<div class="tabs tabs-border mb-4" role="tablist">
<div class="tabs tabs-bordered mb-4" role="tablist">
<button class="tab tab-active" data-view="overview" role="tab">概览</button>
<button class="tab" data-view="users" role="tab">用户</button>
<button class="tab" data-view="groups" role="tab">部门分组</button>
@@ -227,21 +227,33 @@
</main>
</div>
<!-- 应用变更的确认对话框 -->
<dialog id="applyModal" class="modal">
<div class="modal-box">
<h3 class="text-lg font-semibold mb-2">确认应用变更</h3>
<p class="text-sm text-base-content/70 mb-4">
将对 LLDAP 执行下列变更。密码由 LLDAP 独占并交给 Authelia 管理,程序绝不写入;
仅标记「已确认离职」的用户会被物理删除。
</p>
<div id="applyModalBody" class="text-sm mb-4"></div>
<div class="modal-action">
<button id="applyCancel" class="btn btn-ghost">取消</button>
<button id="applyConfirm" class="btn btn-primary">确认执行</button>
<!-- 应用变更的确认对话框。
必须用 FlyonUI 的 overlay 结构:容器是 .overlay.modal 的 <div>(HSOverlay 靠切换
open/hidden class 显隐,不会调用原生 dialog.showModal),显示态由 overlay-open 变体
生成——所以 overlay-open:opacity-100 这个类不能删,否则对话框又会变回不可见。 -->
<div id="applyModal" class="overlay modal modal-middle overlay-open:opacity-100 hidden overlay-open:duration-300"
role="dialog" tabindex="-1" aria-labelledby="applyModalTitle">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 id="applyModalTitle" class="modal-title">确认应用变更</h3>
<button type="button" class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
aria-label="关闭" data-overlay="#applyModal">✕</button>
</div>
<div class="modal-body">
<p class="text-sm text-base-content/70 mb-4">
将对 LLDAP 执行下列变更。密码由 LLDAP 独占并交给 Authelia 管理,程序绝不写入;
仅标记「已确认离职」的用户会被物理删除。
</p>
<div id="applyModalBody" class="text-sm"></div>
</div>
<div class="modal-footer">
<button id="applyCancel" type="button" class="btn btn-soft" data-overlay="#applyModal">取消</button>
<button id="applyConfirm" type="button" class="btn btn-primary">确认执行</button>
</div>
</div>
</div>
</dialog>
</div>
<script src="/static/flyonui.js"></script>
<script src="/static/app.js"></script>
+17 -12
View File
@@ -31,7 +31,7 @@ const STATE_BADGES = {
leave_candidate: ['待发离职确认', 'badge-warning'],
pending_confirm: ['待人工确认离职', 'badge-warning'],
confirmed_leave: ['已确认离职', 'badge-error'],
deleted: ['已删除', 'badge-neutral'],
deleted: ['已删除', 'badge-soft'],
};
// ---------------------------------------------------------------- 主题
@@ -246,7 +246,7 @@ function renderUsers() {
});
$('#userRows').innerHTML = rows.map((u) => {
const st = STATE_BADGES[u.state] || [u.state || '未知', 'badge-neutral'];
const st = STATE_BADGES[u.state] || [u.state || '未知', 'badge-soft'];
return `<tr>
<td class="font-medium">${escapeHtml(u.uid)}</td>
<td>${escapeHtml(u.name || '')}</td>
@@ -337,7 +337,7 @@ function renderPlan(plan) {
const ops = plan.ops || [];
$('#planRows').innerHTML = ops.map((op) => `
<tr>
<td><span class="badge ${OP_BADGES[op.kind] || 'badge-neutral'} badge-sm">${OP_LABELS[op.kind] || op.kind}</span></td>
<td><span class="badge ${OP_BADGES[op.kind] || 'badge-soft'} badge-sm">${OP_LABELS[op.kind] || op.kind}</span></td>
<td class="font-medium">${escapeHtml(op.target)}</td>
<td>${escapeHtml(op.detail)}</td>
</tr>`).join('');
@@ -345,9 +345,18 @@ function renderPlan(plan) {
$('#planEmpty').classList.toggle('hidden', ops.length > 0);
}
// FlyonUI 的 overlay(modal)由 flyonui.js 的 HSOverlay 驱动:它切换 open/hidden class 来显隐。
// 不要改回原生 dialog.showModal()——那不会加上 .open,overlay-open 变体不生效,框就永远不可见。
function openApplyModal() {
if (window.HSOverlay) window.HSOverlay.open('#applyModal');
}
function closeApplyModal() {
if (window.HSOverlay) window.HSOverlay.close('#applyModal');
}
async function applyPlan(force = false) {
const modal = document.getElementById('applyModal');
if (modal && typeof modal.close === 'function') modal.close();
closeApplyModal();
try {
const d = await api('/api/sync', {
method: 'POST',
@@ -372,7 +381,7 @@ async function loadRuns() {
<tr>
<td class="whitespace-nowrap">${fmtTime(r.startedAt)}</td>
<td>${r.mode === 'dry-run'
? '<span class="badge badge-ghost badge-sm">预览</span>'
? '<span class="badge badge-soft badge-sm">预览</span>'
: '<span class="badge badge-primary badge-sm">执行</span>'}</td>
<td>${r.trigger === 'schedule' ? '定时' : '手动'}</td>
<td>${r.status === 'success'
@@ -443,14 +452,10 @@ document.addEventListener('DOMContentLoaded', () => {
<div>删除用户 <strong>${s.deleteUser || 0}</strong></div>
<div>新建分组 <strong>${s.createGroup || 0}</strong> · 加入分组 <strong>${s.addMember || 0}</strong> · 移出分组 <strong>${s.removeMember || 0}</strong></div>
</div>`;
const modal = document.getElementById('applyModal');
if (modal && typeof modal.showModal === 'function') modal.showModal();
openApplyModal();
});
$('#applyCancel').addEventListener('click', () => {
const modal = document.getElementById('applyModal');
if (modal && typeof modal.close === 'function') modal.close();
});
// 「取消」与右上角关闭按钮走 data-overlay 声明式关闭,无需额外监听。
$('#applyConfirm').addEventListener('click', () => applyPlan(false));
// 已有会话则直接进入主界面
+27 -15
View File
@@ -40,14 +40,14 @@
<span class="hidden sm:inline text-sm text-base-content/50">飞书通讯录 → LLDAP</span>
</div>
<div class="flex-none flex items-center gap-2">
<button id="themeToggle" class="btn btn-sm btn-ghost" title="切换明暗主题">主题</button>
<button id="themeToggle" class="btn btn-sm btn-soft" title="切换明暗主题">主题</button>
<button id="logoutBtn" class="btn btn-sm btn-outline">退出</button>
</div>
</header>
<main class="max-w-7xl mx-auto p-4">
<!-- 页签 -->
<div class="tabs tabs-border mb-4" role="tablist">
<div class="tabs tabs-bordered mb-4" role="tablist">
<button class="tab tab-active" data-view="overview" role="tab">概览</button>
<button class="tab" data-view="users" role="tab">用户</button>
<button class="tab" data-view="groups" role="tab">部门分组</button>
@@ -227,21 +227,33 @@
</main>
</div>
<!-- 应用变更的确认对话框 -->
<dialog id="applyModal" class="modal">
<div class="modal-box">
<h3 class="text-lg font-semibold mb-2">确认应用变更</h3>
<p class="text-sm text-base-content/70 mb-4">
将对 LLDAP 执行下列变更。密码由 LLDAP 独占并交给 Authelia 管理,程序绝不写入;
仅标记「已确认离职」的用户会被物理删除。
</p>
<div id="applyModalBody" class="text-sm mb-4"></div>
<div class="modal-action">
<button id="applyCancel" class="btn btn-ghost">取消</button>
<button id="applyConfirm" class="btn btn-primary">确认执行</button>
<!-- 应用变更的确认对话框。
必须用 FlyonUI 的 overlay 结构:容器是 .overlay.modal 的 <div>(HSOverlay 靠切换
open/hidden class 显隐,不会调用原生 dialog.showModal),显示态由 overlay-open 变体
生成——所以 overlay-open:opacity-100 这个类不能删,否则对话框又会变回不可见。 -->
<div id="applyModal" class="overlay modal modal-middle overlay-open:opacity-100 hidden overlay-open:duration-300"
role="dialog" tabindex="-1" aria-labelledby="applyModalTitle">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 id="applyModalTitle" class="modal-title">确认应用变更</h3>
<button type="button" class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
aria-label="关闭" data-overlay="#applyModal">✕</button>
</div>
<div class="modal-body">
<p class="text-sm text-base-content/70 mb-4">
将对 LLDAP 执行下列变更。密码由 LLDAP 独占并交给 Authelia 管理,程序绝不写入;
仅标记「已确认离职」的用户会被物理删除。
</p>
<div id="applyModalBody" class="text-sm"></div>
</div>
<div class="modal-footer">
<button id="applyCancel" type="button" class="btn btn-soft" data-overlay="#applyModal">取消</button>
<button id="applyConfirm" type="button" class="btn btn-primary">确认执行</button>
</div>
</div>
</div>
</dialog>
</div>
<script src="/static/flyonui.js"></script>
<script src="/static/app.js"></script>
+14
View File
@@ -42,6 +42,20 @@
--color-success-content: #071505;
}
/* FlyonUI 的 overlay 变体没有随 @plugin 自动注册:Tailwind v4 只会加载包内与插件同名的
* flyonui.css,而 overlay-open 等 @custom-variant 定义在包根的 variants.css 里,不会被引入。
* 不在这里补上,HTML 里的 overlay-open:opacity-100 就编译不出任何规则,modal 打开后仍是
* opacity:0 的透明层(表现为「页面变暗+点不了别处,但对话框不可见」)。 */
@custom-variant overlay-open {
&.open {
@slot;
}
.open & {
@slot;
}
}
/* 少量自定义样式:表格可横向滚动,长 DN 不撑破布局。 */
@layer components {
.idsync-dn {