Files
piano-plan/app/templates/plan_edit.html
T

185 lines
6.8 KiB
HTML

{% extends "base.html" %}
{% block title %}编辑方案 - 钢琴练习方案系统{% endblock %}
{% block page_css %}
<style>
.edit-plan-footer {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
padding: 1rem;
border-top: 1px solid #dee2e6;
background: #f8f9fa;
position: sticky;
bottom: 0;
}
</style>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4><i class="bi bi-edit"></i> 编辑方案</h4>
<div>
<a href="/plan/{{ plan_id }}" class="btn btn-outline-secondary" onclick="location.replace(this.href); return false;">
<i class="bi bi-arrow-left"></i> 返回详情
</a>
</div>
</div>
<div class="card">
<div class="card-body">
<ul class="nav nav-tabs" id="editContentTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="aiReport-tab" data-bs-toggle="tab" data-bs-target="#aiReportPane" type="button">AI报告</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="dailySchedule-tab" data-bs-toggle="tab" data-bs-target="#dailySchedulePane" type="button">每日练习计划</button>
</li>
</ul>
<div class="tab-content mt-3" id="editContentTabContent">
<div class="tab-pane fade show active" id="aiReportPane" role="tabpanel">
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> 使用 Markdown 格式编辑AI报告,支持标题、列表、加粗等格式
</div>
<textarea id="editAiReport" style="display:none;"></textarea>
<div id="editAiReportEditor"></div>
</div>
<div class="tab-pane fade" id="dailySchedulePane" role="tabpanel">
<div class="alert alert-info mb-2">
<i class="bi bi-info-circle"></i> 支持拖拽排序,双击单元格编辑、点击+添加行、点击×删除行
</div>
<div id="editDailyScheduleTable" style="height: 400px; max-height: 600px;"></div>
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addScheduleRow()">
<i class="bi bi-plus"></i> 添加一行
</button>
</div>
</div>
</div>
<div class="edit-plan-footer">
<button type="button" class="btn btn-secondary" onclick="location.replace('/plan/{{ plan_id }}')">取消</button>
<button type="button" class="btn btn-primary" onclick="savePlanContent()">
<i class="bi bi-save"></i> 保存
</button>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let planContentEditor = null;
let scheduleTable = null;
let editPlanOriginalState = { ai_report: '', scheduleData: [] };
window.pageInit = function() {
loadPlanForEdit();
};
async function loadPlanForEdit() {
const planId = {{ plan_id }};
try {
const resp = await fetch(`/api/plans/${planId}`);
const data = await resp.json();
window.currentStudentId = data.student_id;
const content = typeof data.content === 'string' ? JSON.parse(data.content) : data.content;
document.getElementById('editAiReport').value = content.ai_report || '';
if (planContentEditor) {
planContentEditor.toTextArea();
planContentEditor = null;
}
planContentEditor = new EasyMDE({
element: document.getElementById('editAiReport'),
spellChecker: false,
status: false,
toolbar: ['bold', 'italic', 'heading', '|', 'code', 'quote', 'unordered-list', '|', 'side-by-side', 'fullscreen'],
initialValue: content.ai_report || ''
});
const scheduleData = (content.daily_schedule || []).map(item => ({
phase: item.phase || '',
duration: item.duration || '',
content: item.content || '',
purpose: item.purpose || ''
}));
editPlanOriginalState.ai_report = content.ai_report || '';
editPlanOriginalState.scheduleData = JSON.parse(JSON.stringify(scheduleData));
if (scheduleTable) {
scheduleTable.destroy();
scheduleTable = null;
}
scheduleTable = new Tabulator("#editDailyScheduleTable", {
height: "100%",
data: scheduleData,
layout: "fitColumns",
movableRows: true,
editable: true,
addRowPos: "bottom",
columns: [
{ title: "环节", field: "phase", editor: "input", width: 120 },
{ title: "时长", field: "duration", editor: "input", width: 100 },
{ title: "内容", field: "content", editor: "input", minWidth: 200 },
{ title: "目的", field: "purpose", editor: "input", minWidth: 150 },
{
title: "操作",
width: 80,
formatter: function(cell) {
return "<button type='button' class='btn btn-sm btn-outline-danger'><i class='bi bi-trash'></i></button>";
},
cellClick: function(e, cell) {
cell.getRow().delete();
}
}
]
});
} catch (e) {
alert('加载方案失败: ' + e.message);
}
}
function addScheduleRow() {
if (scheduleTable) {
scheduleTable.addRow({ phase: '', duration: '', content: '', purpose: '' });
}
}
async function savePlanContent() {
const planId = {{ plan_id }};
const currentAiReport = planContentEditor ? planContentEditor.value() : document.getElementById('editAiReport').value;
const tableData = scheduleTable ? scheduleTable.getData() : [];
const hasChanges = currentAiReport !== editPlanOriginalState.ai_report ||
JSON.stringify(tableData) !== JSON.stringify(editPlanOriginalState.scheduleData);
if (!hasChanges) {
alert('没有修改,无需保存');
return;
}
if (!confirm('确定要保存修改吗?')) return;
try {
const resp = await fetch(`/api/plans/${planId}/content`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: JSON.stringify({ ai_report: currentAiReport, daily_schedule: tableData })
})
});
if (resp.ok) {
// 保存后返回上一页(编辑页的上一页是方案详情,已从bfcache恢复)
history.back();
} else {
alert('保存失败');
}
} catch (e) {
alert('保存失败: ' + e.message);
}
}
</script>
{% endblock %}