feat: 目标和问题统一分类体系(综合/乐理相关/演奏能力/其他),添加数据库迁移
This commit is contained in:
@@ -131,6 +131,29 @@ def create_app():
|
|||||||
if "level" not in goal_columns:
|
if "level" not in goal_columns:
|
||||||
db.session.execute(text("ALTER TABLE goals ADD COLUMN level VARCHAR(20) DEFAULT '入门'"))
|
db.session.execute(text("ALTER TABLE goals ADD COLUMN level VARCHAR(20) DEFAULT '入门'"))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
# 检查goals表是否有category字段
|
||||||
|
if "category" not in goal_columns:
|
||||||
|
db.session.execute(text("ALTER TABLE goals ADD COLUMN category VARCHAR(20) DEFAULT '综合'"))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# 迁移problems表分类:旧分类 -> 新分类
|
||||||
|
# 技术类/技术类(手型)/技术类(生理限制) -> 演奏能力
|
||||||
|
# 识谱类 -> 乐理相关
|
||||||
|
# 综合类 -> 综合类 (保持不变)
|
||||||
|
# 其他 -> 其他 (保持不变)
|
||||||
|
category_mapping = {
|
||||||
|
'技术类': '演奏能力',
|
||||||
|
'技术类(手型)': '演奏能力',
|
||||||
|
'技术类(生理限制)': '演奏能力',
|
||||||
|
'识谱类': '乐理相关',
|
||||||
|
}
|
||||||
|
for old_cat, new_cat in category_mapping.items():
|
||||||
|
db.session.execute(
|
||||||
|
text("UPDATE problems SET category = :new_cat WHERE category = :old_cat"),
|
||||||
|
{"new_cat": new_cat, "old_cat": old_cat}
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"数据库迁移: {e}")
|
print(f"数据库迁移: {e}")
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -6,6 +6,9 @@ import re
|
|||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
# 问题和目标的统一分类体系
|
||||||
|
ITEM_CATEGORIES = ['综合', '乐理相关', '演奏能力', '其他']
|
||||||
|
|
||||||
|
|
||||||
class User(db.Model):
|
class User(db.Model):
|
||||||
"""管理员用户表"""
|
"""管理员用户表"""
|
||||||
@@ -149,7 +152,7 @@ class Problem(db.Model):
|
|||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
no = db.Column(db.String(10), unique=True, nullable=False) # 编号:01, 02...
|
no = db.Column(db.String(10), unique=True, nullable=False) # 编号:01, 02...
|
||||||
name = db.Column(db.String(100), nullable=False) # 问题名称
|
name = db.Column(db.String(100), nullable=False) # 问题名称
|
||||||
category = db.Column(db.String(50), default="技术类") # 分类
|
category = db.Column(db.String(20), default="综合") # 分类:综合/乐理相关/演奏能力/其他
|
||||||
content = db.Column(db.Text) # 问题详细内容
|
content = db.Column(db.Text) # 问题详细内容
|
||||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||||
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
@@ -159,7 +162,7 @@ class Problem(db.Model):
|
|||||||
"id": self.id,
|
"id": self.id,
|
||||||
"no": self.no,
|
"no": self.no,
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"category": self.category,
|
"category": self.category or "综合",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -198,6 +201,7 @@ class Goal(db.Model):
|
|||||||
name = db.Column(db.String(100), nullable=False)
|
name = db.Column(db.String(100), nullable=False)
|
||||||
content = db.Column(db.Text)
|
content = db.Column(db.Text)
|
||||||
level = db.Column(db.String(20), default="入门") # 启蒙/入门/进阶/熟练/精通
|
level = db.Column(db.String(20), default="入门") # 启蒙/入门/进阶/熟练/精通
|
||||||
|
category = db.Column(db.String(20), default="综合") # 分类:综合/乐理相关/演奏能力/其他
|
||||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||||
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
@@ -207,6 +211,7 @@ class Goal(db.Model):
|
|||||||
"name": self.name,
|
"name": self.name,
|
||||||
"content": self.content,
|
"content": self.content,
|
||||||
"level": self.level,
|
"level": self.level,
|
||||||
|
"category": self.category or "综合",
|
||||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -25,7 +25,8 @@ def create_goal():
|
|||||||
goal = Goal(
|
goal = Goal(
|
||||||
name=data["name"],
|
name=data["name"],
|
||||||
content=data.get("content", ""),
|
content=data.get("content", ""),
|
||||||
level=data.get("level", "入门")
|
level=data.get("level", "入门"),
|
||||||
|
category=data.get("category", "综合")
|
||||||
)
|
)
|
||||||
db.session.add(goal)
|
db.session.add(goal)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -48,6 +49,8 @@ def update_goal(goal_id):
|
|||||||
goal.content = data["content"]
|
goal.content = data["content"]
|
||||||
if "level" in data:
|
if "level" in data:
|
||||||
goal.level = data["level"]
|
goal.level = data["level"]
|
||||||
|
if "category" in data:
|
||||||
|
goal.category = data["category"]
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify(goal.to_dict())
|
return jsonify(goal.to_dict())
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,15 @@
|
|||||||
<option value="精通">精通</option>
|
<option value="精通">精通</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">分类</label>
|
||||||
|
<select class="form-select" id="goal-category">
|
||||||
|
<option value="综合">综合(涉及多方面)</option>
|
||||||
|
<option value="乐理相关">乐理相关</option>
|
||||||
|
<option value="演奏能力">演奏能力</option>
|
||||||
|
<option value="其他">其他</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">目标内容 (Markdown)</label>
|
<label class="form-label">目标内容 (Markdown)</label>
|
||||||
<textarea class="form-control" id="goal-content" rows="8"></textarea>
|
<textarea class="form-control" id="goal-content" rows="8"></textarea>
|
||||||
@@ -111,6 +120,7 @@ async function loadGoals() {
|
|||||||
|
|
||||||
grid.innerHTML = goalsWithChildren.map(g => {
|
grid.innerHTML = goalsWithChildren.map(g => {
|
||||||
const level = g.level || '入门';
|
const level = g.level || '入门';
|
||||||
|
const category = g.category || '综合';
|
||||||
const childNames = g.children && g.children.length > 0
|
const childNames = g.children && g.children.length > 0
|
||||||
? g.children.map(c => escapeHtml(c.name)).join(', ')
|
? g.children.map(c => escapeHtml(c.name)).join(', ')
|
||||||
: '';
|
: '';
|
||||||
@@ -120,6 +130,7 @@ async function loadGoals() {
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">${escapeHtml(g.name)}</h6>
|
<h6 class="card-title">${escapeHtml(g.name)}</h6>
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
|
<span class="badge bg-primary">${category}</span>
|
||||||
<span class="badge bg-secondary">${level}</span>
|
<span class="badge bg-secondary">${level}</span>
|
||||||
</div>
|
</div>
|
||||||
${childNames ? `<div class="small text-muted mb-2">子目标: ${childNames}</div>` : ''}
|
${childNames ? `<div class="small text-muted mb-2">子目标: ${childNames}</div>` : ''}
|
||||||
@@ -140,14 +151,14 @@ async function saveGoal() {
|
|||||||
const id = document.getElementById('goal-id').value;
|
const id = document.getElementById('goal-id').value;
|
||||||
const name = document.getElementById('goal-name').value;
|
const name = document.getElementById('goal-name').value;
|
||||||
const level = document.getElementById('goal-level').value;
|
const level = document.getElementById('goal-level').value;
|
||||||
|
const category = document.getElementById('goal-category').value;
|
||||||
const content = document.getElementById('goal-content').value;
|
const content = document.getElementById('goal-content').value;
|
||||||
|
|
||||||
if (!name) { alert('请输入目标名称'); return; }
|
if (!name) { alert('请输入目标名称'); return; }
|
||||||
|
|
||||||
const method = id ? 'PUT' : 'POST';
|
const method = id ? 'PUT' : 'POST';
|
||||||
const url = id ? `${API_BASE}/${id}` : API_BASE;
|
const url = id ? `${API_BASE}/${id}` : API_BASE;
|
||||||
const payload = {name, level, content};
|
const payload = {name, level, category, content};
|
||||||
console.log('Saving goal:', payload);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
@@ -174,10 +185,10 @@ function editGoal(id) {
|
|||||||
fetch(`${API_BASE}/${id}`)
|
fetch(`${API_BASE}/${id}`)
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(g => {
|
.then(g => {
|
||||||
console.log('Editing goal:', g);
|
|
||||||
document.getElementById('goal-id').value = g.id;
|
document.getElementById('goal-id').value = g.id;
|
||||||
document.getElementById('goal-name').value = g.name;
|
document.getElementById('goal-name').value = g.name;
|
||||||
document.getElementById('goal-level').value = g.level || '入门';
|
document.getElementById('goal-level').value = g.level || '入门';
|
||||||
|
document.getElementById('goal-category').value = g.category || '综合';
|
||||||
document.getElementById('goal-content').value = g.content || '';
|
document.getElementById('goal-content').value = g.content || '';
|
||||||
document.getElementById('goalModalTitle').textContent = '编辑目标';
|
document.getElementById('goalModalTitle').textContent = '编辑目标';
|
||||||
new bootstrap.Modal(document.getElementById('goalModal')).show();
|
new bootstrap.Modal(document.getElementById('goalModal')).show();
|
||||||
@@ -298,6 +309,7 @@ document.getElementById('goalModal').addEventListener('hidden.bs.modal', () => {
|
|||||||
document.getElementById('goal-id').value = '';
|
document.getElementById('goal-id').value = '';
|
||||||
document.getElementById('goal-name').value = '';
|
document.getElementById('goal-name').value = '';
|
||||||
document.getElementById('goal-level').value = '入门';
|
document.getElementById('goal-level').value = '入门';
|
||||||
|
document.getElementById('goal-category').value = '综合';
|
||||||
document.getElementById('goal-content').value = '';
|
document.getElementById('goal-content').value = '';
|
||||||
document.getElementById('goalModalTitle').textContent = '新建目标';
|
document.getElementById('goalModalTitle').textContent = '新建目标';
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user