fix: 目标关联界面重做,支持多选添加子目标
This commit is contained in:
+101
-20
@@ -54,16 +54,27 @@
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<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>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="relation-goal-id">
|
||||
<h6>上级目标(父目标)</h6>
|
||||
<div id="parent-goals" class="mb-3"></div>
|
||||
<hr>
|
||||
<h6>下级目标(子目标)</h6>
|
||||
<div id="child-goals" class="mb-3"></div>
|
||||
<p class="text-muted">为「<span id="current-goal-name" class="fw-bold"></span>」添加子目标</p>
|
||||
|
||||
<!-- 添加子目标区域 -->
|
||||
<div class="mb-3">
|
||||
<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>
|
||||
@@ -153,28 +164,98 @@ async function deleteGoal(id) {
|
||||
async function manageRelations(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())
|
||||
]);
|
||||
|
||||
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>`
|
||||
).join('') || '<span class="text-muted">无</span>';
|
||||
// 显示当前目标名称
|
||||
document.getElementById('current-goal-name').textContent = currentGoal.name;
|
||||
|
||||
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>`
|
||||
).join('') || '<span class="text-muted">无</span>';
|
||||
// 已关联的子目标
|
||||
const childIds = children.map(c => c.id);
|
||||
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();
|
||||
}
|
||||
|
||||
async function removeRelation(parentId, childId, type) {
|
||||
const url = type === 'parent'
|
||||
? `${API_BASE}/${childId}/children/${parentId}`
|
||||
: `${API_BASE}/${parentId}/children/${childId}`;
|
||||
await fetch(url, {method: 'DELETE'});
|
||||
manageRelations(parentId);
|
||||
function renderChildrenList(children, parentId) {
|
||||
const list = document.getElementById('child-goals-list');
|
||||
if (children.length === 0) {
|
||||
list.innerHTML = '<p class="text-muted">暂无关联子目标</p>';
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user