feat: 目标添加级别字段,卡片显示级别和子目标数量,修复Modal重置
This commit is contained in:
@@ -197,6 +197,7 @@ class Goal(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
content = db.Column(db.Text)
|
||||
level = db.Column(db.String(20), default="入门") # 启蒙/入门/进阶/熟练/精通
|
||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
@@ -205,6 +206,7 @@ class Goal(db.Model):
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"content": self.content,
|
||||
"level": self.level,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
+7
-1
@@ -22,7 +22,11 @@ def get_goals():
|
||||
@login_required_json
|
||||
def create_goal():
|
||||
data = request.get_json()
|
||||
goal = Goal(name=data["name"], content=data.get("content", ""))
|
||||
goal = Goal(
|
||||
name=data["name"],
|
||||
content=data.get("content", ""),
|
||||
level=data.get("level", "入门")
|
||||
)
|
||||
db.session.add(goal)
|
||||
db.session.commit()
|
||||
return jsonify(goal.to_dict()), 201
|
||||
@@ -42,6 +46,8 @@ def update_goal(goal_id):
|
||||
goal.name = data["name"]
|
||||
if "content" in data:
|
||||
goal.content = data["content"]
|
||||
if "level" in data:
|
||||
goal.level = data["level"]
|
||||
db.session.commit()
|
||||
return jsonify(goal.to_dict())
|
||||
|
||||
|
||||
@@ -36,6 +36,16 @@
|
||||
<label class="form-label">目标名称</label>
|
||||
<input type="text" class="form-control" id="goal-name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">级别</label>
|
||||
<select class="form-select" id="goal-level">
|
||||
<option value="启蒙">启蒙</option>
|
||||
<option value="入门" selected>入门</option>
|
||||
<option value="进阶">进阶</option>
|
||||
<option value="熟练">熟练</option>
|
||||
<option value="精通">精通</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">目标内容 (Markdown)</label>
|
||||
<textarea class="form-control" id="goal-content" rows="8"></textarea>
|
||||
@@ -91,12 +101,24 @@ async function loadGoals() {
|
||||
const res = await fetch(API_BASE);
|
||||
const goals = await res.json();
|
||||
const grid = document.getElementById('goals-grid');
|
||||
grid.innerHTML = goals.map(g => `
|
||||
|
||||
// 获取每个目标的子目标数量
|
||||
const goalsWithChildren = await Promise.all(goals.map(async g => {
|
||||
const childrenRes = await fetch(`${API_BASE}/${g.id}/children`);
|
||||
const children = await childrenRes.json();
|
||||
return {...g, childCount: children.length};
|
||||
}));
|
||||
|
||||
grid.innerHTML = goalsWithChildren.map(g => `
|
||||
<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>
|
||||
<p class="card-text small text-muted">${escapeHtml(g.content || '').substring(0, 100)}...</p>
|
||||
<div class="mb-1">
|
||||
<span class="badge bg-secondary">${g.level}</span>
|
||||
${g.childCount > 0 ? `<span class="badge bg-info">${g.childCount}个子目标</span>` : ''}
|
||||
</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>
|
||||
@@ -110,25 +132,22 @@ async function loadGoals() {
|
||||
|
||||
// 保存目标
|
||||
async function saveGoal() {
|
||||
console.log('saveGoal called');
|
||||
const id = document.getElementById('goal-id').value;
|
||||
const name = document.getElementById('goal-name').value;
|
||||
const level = document.getElementById('goal-level').value;
|
||||
const content = document.getElementById('goal-content').value;
|
||||
console.log('Form data:', {id, name, content});
|
||||
|
||||
if (!name) { alert('请输入目标名称'); return; }
|
||||
|
||||
const method = id ? 'PUT' : 'POST';
|
||||
const url = id ? `${API_BASE}/${id}` : API_BASE;
|
||||
console.log('Sending to:', url, 'method:', method);
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name, content})
|
||||
body: JSON.stringify({name, level, content})
|
||||
});
|
||||
console.log('Response status:', res.status);
|
||||
|
||||
if (res.ok) {
|
||||
bootstrap.Modal.getInstance(document.getElementById('goalModal')).hide();
|
||||
@@ -138,7 +157,6 @@ async function saveGoal() {
|
||||
alert(err.error || '保存失败');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Fetch error:', e);
|
||||
alert('网络错误: ' + e.message);
|
||||
}
|
||||
}
|
||||
@@ -149,6 +167,7 @@ function editGoal(id) {
|
||||
.then(g => {
|
||||
document.getElementById('goal-id').value = g.id;
|
||||
document.getElementById('goal-name').value = g.name;
|
||||
document.getElementById('goal-level').value = g.level || '入门';
|
||||
document.getElementById('goal-content').value = g.content || '';
|
||||
document.getElementById('goalModalTitle').textContent = '编辑目标';
|
||||
new bootstrap.Modal(document.getElementById('goalModal')).show();
|
||||
@@ -267,6 +286,9 @@ function escapeHtml(text) {
|
||||
document.getElementById('save-goal').addEventListener('click', saveGoal);
|
||||
document.getElementById('goalModal').addEventListener('hidden.bs.modal', () => {
|
||||
document.getElementById('goal-id').value = '';
|
||||
document.getElementById('goal-name').value = '';
|
||||
document.getElementById('goal-level').value = '入门';
|
||||
document.getElementById('goal-content').value = '';
|
||||
document.getElementById('goalModalTitle').textContent = '新建目标';
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user