refactor: 提取分配目标模态窗体为共享fragment,实现DRY

This commit is contained in:
hmo
2026-04-24 10:22:53 +08:00
parent 08cc0541f2
commit f4ea6c9a77
4 changed files with 137 additions and 132 deletions
+24 -77
View File
@@ -128,61 +128,8 @@
</div>
</div>
<!-- 分配目标弹窗 -->
<div class="modal fade" id="classAssignGoalModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">分配目标 - <span id="classAssignGoalClassName"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">选择目标 *</label>
<select class="form-select" id="class-assign-goal-select"></select>
</div>
<div class="mb-3">
<label class="form-label">评估日期</label>
<div class="row g-2">
<div class="col-auto">
<select class="form-select" id="class-assign-assessment-days">
<option value="">指定天数</option>
<option value="15">15天后</option>
<option value="30">30天后</option>
<option value="60">60天后</option>
<option value="90" selected>90天后</option>
<option value="180">180天后</option>
</select>
</div>
<div class="col-auto">
<input type="date" class="form-control" id="class-assign-assessment-date">
</div>
</div>
</div>
<div class="mb-3">
<a class="text-decoration-none" data-bs-toggle="collapse" href="#classAssignMoreSettings" role="button">
更多设置 ▼
</a>
<div class="collapse" id="classAssignMoreSettings">
<div class="card card-body mt-2">
<div class="mb-3">
<label class="form-label">开始日期</label>
<input type="date" class="form-control" id="class-assign-start-date">
<small class="text-muted">默认立即开始</small>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer-with-top">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="confirm-class-assign-goal">分配</button>
</div>
</div>
</div>
</div>
{% set modal_title = '分配目标' %}
{% include "fragments/assign_goal_modal.html" with context %}
{% endblock %}
{% block extra_js %}
@@ -354,59 +301,59 @@ document.getElementById('addClassBtn').onclick = () => {
// ========== 分配目标功能 ==========
let classAssignGoalModal;
let assignGoalModal;
document.getElementById('classAssignGoalModal').addEventListener('show.bs.modal', function () {
document.getElementById('assignGoalModal').addEventListener('show.bs.modal', function () {
loadClassGoalOptions();
});
function openAssignGoalModal(classId, className) {
currentClassId = classId;
document.getElementById('classAssignGoalClassName').textContent = className;
document.getElementById('assignGoalModalSubtitle').textContent = ' - ' + className;
// 重置表单
document.getElementById('class-assign-assessment-days').value = '';
document.getElementById('class-assign-assessment-date').value = '';
document.getElementById('class-assign-start-date').value = '';
classAssignGoalModal = new bootstrap.Modal(document.getElementById('classAssignGoalModal'));
classAssignGoalModal.show();
document.getElementById('assign-assessment-days').value = '';
document.getElementById('assign-assessment-date').value = '';
document.getElementById('assign-start-date').value = '';
assignGoalModal = new bootstrap.Modal(document.getElementById('assignGoalModal'));
assignGoalModal.show();
}
async function loadClassGoalOptions() {
const res = await fetch('/api/goals');
const goals = await res.json();
const select = document.getElementById('class-assign-goal-select');
const select = document.getElementById('assign-goal-select');
select.innerHTML = goals.map(g => `<option value="${g.id}">${escapeHtml(g.name)} [${g.level || '入门'} - ${g.category || '综合'}]</option>`).join('');
// 设置默认开始日期为今天,评估日期为90天后
document.getElementById('class-assign-start-date').value = new Date().toISOString().split('T')[0];
document.getElementById('class-assign-assessment-days').value = '90';
document.getElementById('assign-start-date').value = new Date().toISOString().split('T')[0];
document.getElementById('assign-assessment-days').value = '90';
const d = new Date();
d.setDate(d.getDate() + 90);
document.getElementById('class-assign-assessment-date').value = d.toISOString().split('T')[0];
document.getElementById('assign-assessment-date').value = d.toISOString().split('T')[0];
}
// 评估日期联动
document.getElementById('class-assign-assessment-days').addEventListener('change', function() {
document.getElementById('assign-assessment-days').addEventListener('change', function() {
const days = parseInt(this.value);
if (days) {
const d = new Date();
d.setDate(d.getDate() + days);
document.getElementById('class-assign-assessment-date').value = d.toISOString().split('T')[0];
document.getElementById('assign-assessment-date').value = d.toISOString().split('T')[0];
}
});
document.getElementById('class-assign-assessment-date').addEventListener('change', function() {
document.getElementById('assign-assessment-date').addEventListener('change', function() {
if (this.value) {
document.getElementById('class-assign-assessment-days').value = '';
document.getElementById('assign-assessment-days').value = '';
}
});
// 确认分配目标
document.getElementById('confirm-class-assign-goal').addEventListener('click', async () => {
const goalId = document.getElementById('class-assign-goal-select').value;
const assessmentDays = document.getElementById('class-assign-assessment-days').value;
const assessmentDate = document.getElementById('class-assign-assessment-date').value;
const startDate = document.getElementById('class-assign-start-date').value;
document.getElementById('confirm-assign-goal').addEventListener('click', async () => {
const goalId = document.getElementById('assign-goal-select').value;
const assessmentDays = document.getElementById('assign-assessment-days').value;
const assessmentDate = document.getElementById('assign-assessment-date').value;
const startDate = document.getElementById('assign-start-date').value;
if (!goalId) { alert('请选择目标'); return; }
if (!assessmentDays && !assessmentDate) { alert('请选择评估方式'); return; }
@@ -428,7 +375,7 @@ document.getElementById('confirm-class-assign-goal').addEventListener('click', a
if (res.ok) {
const data = await res.json();
classAssignGoalModal.hide();
assignGoalModal.hide();
alert(data.message + (data.skipped_count ? `${data.skipped_count}个学员已分配此目标,跳过)` : ''));
} else {
const err = await res.json();
@@ -0,0 +1,64 @@
<!-- 分配目标 Modal (共享 Fragment) -->
<!--
通过 data 属性传递上下文:
- data-context: "student" 或 "class"
- data-target-id: 学员ID 或 班级ID
- data-modal-title: 模态窗体标题
-->
<div class="modal fade" id="assignGoalModal" tabindex="-1"
data-context="{{ context | default('student') }}"
data-target-id="{{ target_id | default('') }}"
data-modal-title="{{ modal_title | default('分配目标') }}">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ modal_title | default('分配目标') }}<span id="assignGoalModalSubtitle"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">选择目标 *</label>
<select class="form-select" id="assign-goal-select"></select>
</div>
<div class="mb-3">
<label class="form-label">评估日期</label>
<div class="row g-2">
<div class="col-auto">
<select class="form-select" id="assign-assessment-days">
<option value="">指定天数</option>
<option value="15">15天后</option>
<option value="30">30天后</option>
<option value="60">60天后</option>
<option value="90" selected>90天后</option>
<option value="180">180天后</option>
</select>
</div>
<div class="col-auto">
<input type="date" class="form-control" id="assign-assessment-date">
</div>
</div>
</div>
<div class="mb-3">
<a class="text-decoration-none" data-bs-toggle="collapse" href="#assignMoreSettings" role="button">
更多设置 ▼
</a>
<div class="collapse" id="assignMoreSettings">
<div class="card card-body mt-2">
<div class="mb-3">
<label class="form-label">开始日期</label>
<input type="date" class="form-control" id="assign-start-date">
<small class="text-muted">默认立即开始</small>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer-with-top">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="confirm-assign-goal">分配</button>
</div>
</div>
</div>
</div>
+1 -55
View File
@@ -252,61 +252,7 @@
</div>
</div>
<!-- 分配目标 Modal -->
<div class="modal fade" id="assignGoalModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">分配目标</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">选择目标 *</label>
<select class="form-select" id="assign-goal-select"></select>
</div>
<div class="mb-3">
<label class="form-label">评估日期</label>
<div class="row g-2">
<div class="col-auto">
<select class="form-select" id="assign-assessment-days">
<option value="">指定天数</option>
<option value="15">15天后</option>
<option value="30">30天后</option>
<option value="60">60天后</option>
<option value="90" selected>90天后</option>
<option value="180">180天后</option>
</select>
</div>
<div class="col-auto">
<input type="date" class="form-control" id="assign-assessment-date">
</div>
</div>
</div>
<div class="mb-3">
<a class="text-decoration-none" data-bs-toggle="collapse" href="#assignMoreSettings" role="button">
更多设置 ▼
</a>
<div class="collapse" id="assignMoreSettings">
<div class="card card-body mt-2">
<div class="mb-3">
<label class="form-label">开始日期</label>
<input type="date" class="form-control" id="assign-start-date">
<small class="text-muted">默认立即开始</small>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer-with-top">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="confirm-assign-goal">分配</button>
</div>
</div>
</div>
</div>
{% include "fragments/assign_goal_modal.html" with context %}
<!-- 调整目标 Modal -->
<div class="modal fade" id="adjustGoalModal" tabindex="-1">