diff --git a/app/templates/goals.html b/app/templates/goals.html
index 77a7e19..f29bde1 100644
--- a/app/templates/goals.html
+++ b/app/templates/goals.html
@@ -54,16 +54,27 @@
-
上级目标(父目标)
-
-
-
下级目标(子目标)
-
+
为「」添加子目标
+
+
+
+
+
+
按住 Ctrl/Cmd 可多选
+
+
+
+
+
已关联的子目标
+
@@ -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 =>
- `${escapeHtml(g.name)} `
- ).join('') || '无';
+ // 显示当前目标名称
+ document.getElementById('current-goal-name').textContent = currentGoal.name;
- document.getElementById('child-goals').innerHTML = children.map(g =>
- `${escapeHtml(g.name)} `
- ).join('') || '无';
+ // 已关联的子目标
+ 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 => ``)
+ .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 = '暂无关联子目标
';
+ return;
+ }
+ list.innerHTML = children.map(g =>
+ `
+ ${escapeHtml(g.name)}
+
+
`
+ ).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 => ``)
+ .join('');
}
function escapeHtml(text) {