feat: 目标和问题列表添加筛选和分组功能
This commit is contained in:
+122
-24
@@ -4,6 +4,49 @@
|
||||
<div class="container-fluid py-4">
|
||||
<h2 class="mb-4">🎯 目标管理</h2>
|
||||
|
||||
<!-- 筛选和分组控制 -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-center">
|
||||
<div class="col-auto">
|
||||
<label class="form-label mb-0">筛选:</label>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<select class="form-select" id="filter-level" onchange="applyFilters()">
|
||||
<option value="">全部级别</option>
|
||||
<option value="启蒙">启蒙</option>
|
||||
<option value="入门">入门</option>
|
||||
<option value="进阶">进阶</option>
|
||||
<option value="熟练">熟练</option>
|
||||
<option value="精通">精通</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<select class="form-select" id="filter-category" onchange="applyFilters()">
|
||||
<option value="">全部分类</option>
|
||||
<option value="综合">综合</option>
|
||||
<option value="乐理相关">乐理相关</option>
|
||||
<option value="演奏能力">演奏能力</option>
|
||||
<option value="其他">其他</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label class="form-label mb-0">分组:</label>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<select class="form-select" id="group-by" onchange="applyFilters()">
|
||||
<option value="">不分组</option>
|
||||
<option value="level">按级别分组</option>
|
||||
<option value="category">按分类分组</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto ms-auto">
|
||||
<span id="goals-count" class="text-muted small"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 目标列表 -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@@ -104,46 +147,101 @@
|
||||
{{ super() }}
|
||||
<script>
|
||||
const API_BASE = '/api/goals';
|
||||
let allGoals = []; // 缓存所有目标数据
|
||||
|
||||
// 加载目标列表
|
||||
async function loadGoals() {
|
||||
const res = await fetch(API_BASE);
|
||||
const goals = await res.json();
|
||||
const grid = document.getElementById('goals-grid');
|
||||
|
||||
// 获取每个目标的子目标信息
|
||||
const goalsWithChildren = await Promise.all(goals.map(async g => {
|
||||
allGoals = await Promise.all(goals.map(async g => {
|
||||
const childrenRes = await fetch(`${API_BASE}/${g.id}/children`);
|
||||
const children = await childrenRes.json();
|
||||
return {...g, children: children};
|
||||
}));
|
||||
|
||||
grid.innerHTML = goalsWithChildren.map(g => {
|
||||
const level = g.level || '入门';
|
||||
const category = g.category || '综合';
|
||||
const childNames = g.children && g.children.length > 0
|
||||
? g.children.map(c => escapeHtml(c.name)).join(', ')
|
||||
: '';
|
||||
return `
|
||||
<div class="col-md-4 col-lg-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">${escapeHtml(g.name)}</h6>
|
||||
<div class="mb-1">
|
||||
<span class="badge bg-primary">${category}</span>
|
||||
<span class="badge bg-secondary">${level}</span>
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
// 应用筛选和分组
|
||||
function applyFilters() {
|
||||
const filterLevel = document.getElementById('filter-level').value;
|
||||
const filterCategory = document.getElementById('filter-category').value;
|
||||
const groupBy = document.getElementById('group-by').value;
|
||||
|
||||
// 筛选
|
||||
let filtered = allGoals.filter(g => {
|
||||
if (filterLevel && g.level !== filterLevel) return false;
|
||||
if (filterCategory && g.category !== filterCategory) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
// 更新计数
|
||||
document.getElementById('goals-count').textContent = `共 ${filtered.length} 个目标`;
|
||||
|
||||
// 渲染
|
||||
const grid = document.getElementById('goals-grid');
|
||||
|
||||
if (groupBy) {
|
||||
// 分组显示
|
||||
const groups = {};
|
||||
filtered.forEach(g => {
|
||||
const key = groupBy === 'level' ? (g.level || '入门') : (g.category || '综合');
|
||||
if (!groups[key]) groups[key] = [];
|
||||
groups[key].push(g);
|
||||
});
|
||||
|
||||
grid.innerHTML = Object.entries(groups).map(([key, goals]) => `
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="mb-0">${groupBy === 'level' ? '级别' : '分类'}:${key}(${goals.length}个)</h6>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="row g-2">
|
||||
${goals.map(g => renderGoalCard(g)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
${childNames ? `<div class="small text-muted mb-2">子目标: ${childNames}</div>` : ''}
|
||||
<p class="card-text small text-muted">${escapeHtml(g.content || '').substring(0, 80)}${g.content && g.content.length > 80 ? '...' : ''}</p>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="editGoal(${g.id})">编辑</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="manageRelations(${g.id})">关联</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteGoal(${g.id})">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
// 不分组
|
||||
grid.innerHTML = filtered.map(g => `
|
||||
<div class="col-md-4 col-lg-3">
|
||||
${renderGoalCard(g)}
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染单个目标卡片
|
||||
function renderGoalCard(g) {
|
||||
const level = g.level || '入门';
|
||||
const category = g.category || '综合';
|
||||
const childNames = g.children && g.children.length > 0
|
||||
? g.children.map(c => escapeHtml(c.name)).join(', ')
|
||||
: '';
|
||||
return `
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">${escapeHtml(g.name)}</h6>
|
||||
<div class="mb-1">
|
||||
<span class="badge bg-primary">${category}</span>
|
||||
<span class="badge bg-secondary">${level}</span>
|
||||
</div>
|
||||
${childNames ? `<div class="small text-muted mb-2">子目标: ${childNames}</div>` : ''}
|
||||
<p class="card-text small text-muted">${escapeHtml(g.content || '').substring(0, 80)}${g.content && g.content.length > 80 ? '...' : ''}</p>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="editGoal(${g.id})">编辑</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="manageRelations(${g.id})">关联</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteGoal(${g.id})">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
`}).join('');
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存目标
|
||||
|
||||
Reference in New Issue
Block a user