`;
for (const key in data) {
const value = data[key];
const currentPath = parentPath ? `${parentPath}::${key}` : key;
const isChecked = hiddenKeys.includes(currentPath) ? "checked" : "";
if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) {
// 🌟 系統設定區塊也套用相同的視覺優化
const isVirtualGroup = Object.keys(value).every(k => /^\[\d+\]$/.test(k));
const isVirtualIndex = /^\[\d+\]$/.test(key);
let displayName = key;
let iconHtml = `
`;
if (isVirtualGroup) {
iconHtml = `
📑`;
displayName = `${key}
(指令群組)`;
} else if (isVirtualIndex) {
iconHtml = `
📦`;
displayName = `
項目 ${key}`;
}
html += `
${buildRealFilterTree(value, currentPath, hiddenKeys)}
`;
} else {
const isVirtualIndex = /^\[\d+\]$/.test(key);
let displayName = isVirtualIndex ? `
項目 ${key}` : `
${key}`;
const safeValue = (value === null ? '' : value).toString().replace(/"/g, """);
html += `
📄 ${displayName}:
${safeValue}
`;
}
}
html += '
';
return html;
}
// 🔽 當點擊「父節點」時:同步勾選/取消所有子節點
function toggleChildCheckboxes(parentCheckbox) {
const details = parentCheckbox.closest('details');
if (details) {
// 找到該資料夾內所有的子 Checkbox
const childCheckboxes = details.querySelectorAll('.filter-children-container .filter-checkbox');
childCheckboxes.forEach(cb => {
cb.checked = parentCheckbox.checked;
});
}
// 往上檢查是否需要更新更上層的父節點
updateParentCheckboxState(parentCheckbox);
}
// 🔼 當點擊「子節點」時:往上檢查父節點是否該被勾選
function updateParentCheckboxState(element) {
// 找到包住這個元素的上一層 details
const parentDetails = element.closest('.filter-children-container')?.closest('details');
if (!parentDetails) return;
const parentCheckbox = parentDetails.querySelector('summary .filter-checkbox');
const childCheckboxes = parentDetails.querySelectorAll('.filter-children-container .filter-checkbox');
if (parentCheckbox && childCheckboxes.length > 0) {
// 如果所有的子節點都被勾選了,父節點就自動打勾;只要有一個沒勾,父節點就取消打勾
const allChecked = Array.from(childCheckboxes).every(cb => cb.checked);
parentCheckbox.checked = allChecked;
// 遞迴往上檢查,確保多層級的資料夾也能正確連動
updateParentCheckboxState(parentCheckbox);
}
}
async function saveSystemSettings() {
const checkboxes = document.querySelectorAll('.filter-checkbox:checked');
const selectedHiddenKeys = Array.from(checkboxes).map(cb => cb.value);
try {
const response = await fetch('/api/v1/settings/tree-filters', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ hidden_keys: selectedHiddenKeys })
});
const result = await response.json();
if (result.status === 'success') {
const statusText = document.getElementById('settings-status');
statusText.style.display = 'inline-block';
currentHiddenKeys = selectedHiddenKeys; // 更新本地狀態
setTimeout(() => { statusText.style.display = 'none'; }, 3000);
}
} catch (error) {
console.error("儲存設定失敗:", error);
alert("儲存設定時發生錯誤!");
}
}
// ==========================================
// 8. Modal 彈出視窗控制
// ==========================================
function openModal(targetInfo) {
document.getElementById('outputModal').classList.add('active');
document.getElementById('modalTargetInfo').textContent = targetInfo ? `(${targetInfo})` : '';
document.body.style.overflow = 'hidden';
}
function closeModal(event) {
if (event && event.target !== event.currentTarget && event.target.className !== 'modal-close') return;
document.getElementById('outputModal').classList.remove('active');
document.body.style.overflow = '';
}
// ==========================================
// 💡 掃描按鈕狀態管理 (支援跨重新整理鎖定)
// ==========================================
// 1. 網頁載入時,檢查是否還有背景任務在跑
document.addEventListener("DOMContentLoaded", () => {
checkScanButtonState();
});
function checkScanButtonState() {
const btn = document.getElementById('global-scan-btn');
if (!btn) return;
const scanLockUntil = localStorage.getItem('scanLockUntil');
const now = Date.now();
if (scanLockUntil && now < parseInt(scanLockUntil)) {
// 如果還在鎖定期間內,恢復鎖定狀態
lockScanButton(parseInt(scanLockUntil) - now);
}
}
// 2. 鎖定按鈕的 UI 變化
function lockScanButton(durationMs) {
const btn = document.getElementById('global-scan-btn');
if (!btn) return;
btn.disabled = true;
btn.style.backgroundColor = "#e67e22"; // 變成醒目的橘色
btn.style.cursor = "not-allowed";
btn.innerText = "⏳ 背景掃描執行中...";
// 設定計時器,時間到自動解鎖
setTimeout(() => {
// 只有當樹狀圖已經載入時,才恢復成可點擊的紫色
const treeContainer = document.getElementById('tree-container');
if (treeContainer && treeContainer.innerHTML.includes('leaf-container')) {
enableGlobalScanButton();
} else {
// 樹狀圖沒載入,退回初始灰階狀態
btn.disabled = true;
btn.style.backgroundColor = "#bdc3c7";
btn.innerText = "⏳ 請先載入樹狀圖";
}
localStorage.removeItem('scanLockUntil');
}, durationMs);
}
// 3. 提供給「樹狀圖載入完成」時呼叫的解鎖函數
function enableGlobalScanButton() {
const btn = document.getElementById('global-scan-btn');
if (!btn) return;
const scanLockUntil = localStorage.getItem('scanLockUntil');
// 如果目前沒有被鎖定,才把它變成可點擊的紫色
if (!scanLockUntil || Date.now() >= parseInt(scanLockUntil)) {
btn.disabled = false;
btn.style.backgroundColor = "#8e44ad"; // 恢復紫色
btn.style.cursor = "pointer";
btn.innerText = "🔍 掃描畫面缺失選項";
}
}
// ==========================================
// 🌟 新增:清除畫面選項快取功能 (精準可見度判定版)
// ==========================================
async function clearVisibleCache() {
const elements = document.querySelectorAll('.leaf-container');
const pathsToClear = [];
elements.forEach(el => {
// 檢查這個元素是否被包在「未展開」的資料夾裡面
const isHiddenByFolder = el.closest('details:not([open])') !== null;
if (!isHiddenByFolder) {
const path = el.getAttribute('data-path');
if (path) {
const cacheKey = path.replace(/::/g, ' ');
if (!pathsToClear.includes(cacheKey)) {
pathsToClear.push(cacheKey);
}
}
}
});
if (pathsToClear.length === 0) {
alert("畫面上沒有展開的選項可以清除!\n請先展開您想重抓的資料夾。");
return;
}
if (!confirm(`確定要清除畫面上 ${pathsToClear.length} 個選項的快取嗎?\n(清除後請重新點擊「一鍵掃描」)`)) return;
try {
const response = await fetch('/api/v1/clear_cache', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(pathsToClear)
});
if (!response.ok) throw new Error(`伺服器回應錯誤: ${response.status}`);
const result = await response.json();
alert(`✅ 成功清除 ${result.cleared_count} 筆快取!\n現在請點擊旁邊的「一鍵掃描缺失選項」來重新擷取。`);
} catch (error) {
console.error("清除快取失敗:", error);
alert("❌ 清除快取失敗,請檢查網路連線或後端 API 是否正確啟動。");
}
}