<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>部門選擇器 Demo</title>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<style>
#deptTree { border: 1px solid #ddd; padding: 10px; max-height: 300px; overflow-y: auto; }
.dept-node { margin-left: 20px; }
.dept-label { cursor: pointer; user-select: none; }
.toggle-btn { margin-right: 5px; text-decoration: none; color: #333; }
</style>
</head>
<body>
<table>
<tr>
<td valign="top">
收件人:<input type="text" id="recipientNames" style="width: 400px;" readonly />
<input type="hidden" id="recipientIds" name="recipientIds" />
<br/><br/> 主題:<input type="text" style="width: 400px;" />
</td>
<td id="deptTree">
<label><input type="checkbox" class="chk-all" /> 全選</label><br/>
<!-- 動態生成的部門樹將插入此處 -->
</td>
</tr>
</table>
<script>
// 模擬後端返回的部門資料
const deptData = [
{ id: 1, name: '交通局', pid: 0, children: [
{ id: 11, name: '交通局一科', pid: 1 },
{ id: 12, name: '交通局二科', pid: 1 }
]},
{ id: 2, name: '教 育 局', pid: 0, children: [
{ id: 21, name: '基教科', pid: 2 },
{ id: 22, name: '高教科', pid: 2 }
]}
];
// 遞歸生成樹形 HTML
function buildTree(nodes, level = 0) {
let html = '';
nodes.forEach(node => {
const hasChildren = node.children && node.children.length > 0;
const indent = level * 20;
html += `<div class="dept-node" style="margin-left:${indent}px">`;
if (hasChildren) {
html += `<a href="#" class="toggle-btn" data-id="${node.id}">▼</a>`;
} else {
html += `<span style="width:12px;display:inline-block"></span>`;
}
html += `<label class="dept-label"><input type="checkbox" class="chk-item" value="${node.id}" /> ${node.name}</label>`;
if (hasChildren) {
html += `<div class="children" data-pid="${node.id}" style="display:block">${buildTree(node.children, level + 1)}</div>`;
}
html += '</div>';
});
return html;
}
// 初始化渲染
$('#deptTree').append(buildTree(deptData));
// 同步收件人框
function syncRecipients() {
const names = [], ids = [];
$('#deptTree .chk-item:checked').each(function() {
names.push($(this).next().text().trim());
ids.push(this.value);
});
$('#recipientNames').val(names.join('; '));
$('#recipientIds').val(ids.join(','));
}
// 事件綁定(使用事件委託)
$('#deptTree')
// 展開/摺疊
.on('click', '.toggle-btn', function(e) {
e.preventDefault();
const $children = $(this).parent().find('> .children');
const isHidden = $children.is(':hidden');
$children.toggle(isHidden);
$(this).text(isHidden ? '▼' : '▶');
})
// 全選/反選
.on('change', '.chk-all', function() {
$('#deptTree .chk-item').prop('checked', this.checked);
syncRecipients();
})
// 子節點聯動父節點(可選增強)
.on('change', '.chk-item', function() {
syncRecipients();
// 可在此添加:父節點半選狀態邏輯
});
// 初始同步
syncRecipients();
</script>
</body>
</html>
| 功能點 | 實現方式 |
|---|---|
| 樹形渲染 | 遞歸函數 buildTree() 生成巢狀 HTML |
| 展開/摺疊 | 切換 .children 容器 display,圖標切換 ▼/▶ |
| 全選/反選 | .chk-all 觸發所有 .chk-item prop('checked') |
| 值同步 | syncRecipients() 遍歷 :checked 收集 name/value |
| 事件委託 | 綁定在 #deptTree 上,支援動態新增節點 |
{id, name, pid, children} 結構,前端遞歸渲染zTree、jstree 等成熟插件替代手寫邏輯此方案純前端實現,零依賴(僅 jQuery),易於整合至既有 JSP/Thymeleaf/Vue 專案中。