fix: 目标关联界面重做,支持多选添加子目标

This commit is contained in:
hmo
2026-04-23 20:50:05 +08:00
parent dd492c3b88
commit 7d439b4b17
+101 -20
View File
@@ -54,16 +54,27 @@
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">管理目标关系</h5> <h5 class="modal-title" id="relationModalTitle">管理目标</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<input type="hidden" id="relation-goal-id"> <input type="hidden" id="relation-goal-id">
<h6>上级目标(父目标</h6> <p class="text-muted">为「<span id="current-goal-name" class="fw-bold"></span>」添加子目标</p>
<div id="parent-goals" class="mb-3"></div>
<hr> <!-- 添加子目标区域 -->
<h6>下级目标(子目标)</h6> <div class="mb-3">
<div id="child-goals" class="mb-3"></div> <label class="form-label">添加子目标(可多选)</label>
<select class="form-select" id="available-goals-select" multiple size="5">
</select>
<div class="form-text">按住 Ctrl/Cmd 可多选</div>
</div>
<button class="btn btn-primary btn-sm mb-3" onclick="addSelectedChildren()">
<i class="bi bi-plus"></i> 添加选中目标
</button>
<!-- 已关联的子目标 -->
<h6 class="mt-3">已关联的子目标</h6>
<div id="child-goals-list" class="list-group"></div>
</div> </div>
</div> </div>
</div> </div>
@@ -153,28 +164,98 @@ async function deleteGoal(id) {
async function manageRelations(id) { async function manageRelations(id) {
document.getElementById('relation-goal-id').value = id; document.getElementById('relation-goal-id').value = id;
const [parents, children] = await Promise.all([ // 获取当前目标信息和所有目标
fetch(`${API_BASE}/${id}/parents`).then(r => r.json()), const [currentGoal, allGoals, children] = await Promise.all([
fetch(`${API_BASE}/${id}`).then(r => r.json()),
fetch(API_BASE).then(r => r.json()),
fetch(`${API_BASE}/${id}/children`).then(r => r.json()) fetch(`${API_BASE}/${id}/children`).then(r => r.json())
]); ]);
document.getElementById('parent-goals').innerHTML = parents.map(g => // 显示当前目标名称
`<span class="badge bg-info me-1 mb-1">${escapeHtml(g.name)} <button type="button" class="btn-close btn-close-white ms-1" onclick="removeRelation(${id}, ${g.id}, 'parent')"></button></span>` document.getElementById('current-goal-name').textContent = currentGoal.name;
).join('') || '<span class="text-muted">无</span>';
document.getElementById('child-goals').innerHTML = children.map(g => // 已关联的子目标
`<span class="badge bg-primary me-1 mb-1">${escapeHtml(g.name)} <button type="button" class="btn-close btn-close-white ms-1" onclick="removeRelation(${id}, ${g.id}, 'child')"></button></span>` const childIds = children.map(c => c.id);
).join('') || '<span class="text-muted">无</span>'; renderChildrenList(children, id);
// 可选的子目标(下拉列表中排除自己 和 已关联的)
const availableSelect = document.getElementById('available-goals-select');
availableSelect.innerHTML = allGoals
.filter(g => g.id !== id && !childIds.includes(g.id))
.map(g => `<option value="${g.id}">${escapeHtml(g.name)}</option>`)
.join('');
new bootstrap.Modal(document.getElementById('relationModal')).show(); new bootstrap.Modal(document.getElementById('relationModal')).show();
} }
async function removeRelation(parentId, childId, type) { function renderChildrenList(children, parentId) {
const url = type === 'parent' const list = document.getElementById('child-goals-list');
? `${API_BASE}/${childId}/children/${parentId}` if (children.length === 0) {
: `${API_BASE}/${parentId}/children/${childId}`; list.innerHTML = '<p class="text-muted">暂无关联子目标</p>';
await fetch(url, {method: 'DELETE'}); return;
manageRelations(parentId); }
list.innerHTML = children.map(g =>
`<div class="list-group-item d-flex justify-content-between align-items-center">
${escapeHtml(g.name)}
<button class="btn btn-sm btn-outline-danger" onclick="removeChildRelation(${parentId}, ${g.id})">
<i class="bi bi-trash"></i> 移除
</button>
</div>`
).join('');
}
async function addSelectedChildren() {
const parentId = document.getElementById('relation-goal-id').value;
const select = document.getElementById('available-goals-select');
const selectedOptions = Array.from(select.selectedOptions);
if (selectedOptions.length === 0) {
alert('请先选择要添加的子目标');
return;
}
for (const option of selectedOptions) {
const childId = parseInt(option.value);
try {
const res = await fetch(`${API_BASE}/${parentId}/children`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({child_goal_id: childId})
});
if (!res.ok) {
const err = await res.json();
alert(err.error || '添加失败');
}
} catch (e) {
console.error(e);
}
}
// 刷新列表
const children = await fetch(`${API_BASE}/${parentId}/children`).then(r => r.json());
renderChildrenList(children, parentId);
// 从下拉框移除已添加的
selectedOptions.forEach(opt => opt.remove());
}
async function removeChildRelation(parentId, childId) {
if (!confirm('确定移除此关联?')) return;
await fetch(`${API_BASE}/${parentId}/children/${childId}`, {method: 'DELETE'});
// 刷新列表
const children = await fetch(`${API_BASE}/${parentId}/children`).then(r => r.json());
renderChildrenList(children, parentId);
// 刷新下拉框
const allGoals = await fetch(API_BASE).then(r => r.json());
const childIds = children.map(c => c.id);
const select = document.getElementById('available-goals-select');
const currentGoalId = parseInt(document.getElementById('relation-goal-id').value);
select.innerHTML = allGoals
.filter(g => g.id !== currentGoalId && !childIds.includes(g.id))
.map(g => `<option value="${g.id}">${escapeHtml(g.name)}</option>`)
.join('');
} }
function escapeHtml(text) { function escapeHtml(text) {