作业系统与提交管理
高效的作业系统是教学评估的关键。本文讲解完整的作业管理流程。
作业模型设计
作业数据结构
const assignmentModel = {
id: 'ASG_001',
courseId: 'CRS_001',
title: 'HTML 基础练习',
description: '创建你的第一个 HTML 页面',
type: 'coding', // coding, essay, multiple_choice, file_upload
instructions: `
1. 创建一个 HTML 文件
2. 包含 header, main, footer 部分
3. 添加至少 5 个不同的 HTML 标签
`,
// 评分标准
rubric: [
{
criterion: 'HTML 结构',
maxPoints: 30,
description: '正确使用语义化 HTML 标签',
levels: [
{ score: 30, description: '完美' },
{ score: 20, description: '良好' },
{ score: 10, description: '及格' },
{ score: 0, description: '不及格' },
],
},
{
criterion: '代码质量',
maxPoints: 20,
description: '代码整洁、有注释',
},
{
criterion: '完成度',
maxPoints: 50,
description: '完成所有要求',
},
],
// 时间配置
dueDate: '2025-12-15',
lateSubmissionPolicy: {
allowLate: true,
gracePeriod: 24, // 小时
latePenalty: 10, // 百分比
},
// 附加资源
attachments: [
{ name: 'template.html', url: 'https://...' },
{ name: 'guide.md', url: 'https://...' },
],
maxAttempts: 3,
createdAt: '2025-12-01',
};
作业创建界面
教师作业创建表单
function CreateAssignment() {
const [formData, setFormData] = useState({
title: '',
description: '',
type: 'coding',
instructions: '',
dueDate: '',
maxAttempts: 3,
rubric: [],
});
const [rubricItems, setRubricItems] = useState([
{ criterion: '', maxPoints: 0, levels: [] },
]);
const handleAddRubricItem = () => {
setRubricItems([
...rubricItems,
{ criterion: '', maxPoints: 0, levels: [] },
]);
};
const handleSubmit = async (e) => {
e.preventDefault();
const assignment = {
...formData,
rubric: rubricItems.filter(item => item.criterion),
totalPoints: rubricItems.reduce((sum, item) => sum + item.maxPoints, 0),
};
try {
const response = await api.post('/assignments', assignment);
console.log('Assignment created:', response);
// 显示成功消息或重定向
} catch (error) {
console.error('Failed to create assignment:', error);
}
};
return (
<form onSubmit={handleSubmit} className="assignment-form">
<section className="basic-info">
<input
type="text"
placeholder="作业标题"
value={formData.title}
onChange={(e) => setFormData({...formData, title: e.target.value})}
required
/>
<textarea
placeholder="作业描述"
value={formData.description}
onChange={(e) => setFormData({...formData, description: e.target.value})}
/>
<select
value={formData.type}
onChange={(e) => setFormData({...formData, type: e.target.value})}
>
<option value="coding">编码</option>
<option value="essay">论文</option>
<option value="multiple_choice">选择题</option>
<option value="file_upload">文件上传</option>
</select>
<input
type="datetime-local"
value={formData.dueDate}
onChange={(e) => setFormData({...formData, dueDate: e.target.value})}
required
/>
</section>
<section className="rubric-section">
<h3>评分标准</h3>
{rubricItems.map((item, index) => (
<div key={index} className="rubric-item">
<input
type="text"
placeholder="评分标准"
value={item.criterion}
onChange={(e) => {
const newItems = [...rubricItems];
newItems[index].criterion = e.target.value;
setRubricItems(newItems);
}}
/>
<input
type="number"
placeholder="最高分"
value={item.maxPoints}
onChange={(e) => {
const newItems = [...rubricItems];
newItems[index].maxPoints = parseInt(e.target.value);
setRubricItems(newItems);
}}
/>
</div>
))}
<button type="button" onClick={handleAddRubricItem}>
添加评分标准
</button>
</section>
<button type="submit">创建作业</button>
</form>
);
}
学生提交系统
作业提交流程
function SubmitAssignment() {
const { assignmentId } = useParams();
const [assignment, setAssignment] = useState(null);
const [submission, setSubmission] = useState(null);
const [file, setFile] = useState(null);
const [codeContent, setCodeContent] = useState('');
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
fetchAssignment(assignmentId).then(setAssignment);
fetchStudentSubmission(assignmentId).then(setSubmission);
}, [assignmentId]);
const handleSubmit = async (e) => {
e.preventDefault();
setSubmitting(true);
try {
const formData = new FormData();
formData.append('assignmentId', assignmentId);
formData.append('submissionDate', new Date().toISOString());
if (file) {
formData.append('file', file);
}
if (assignment.type === 'coding') {
formData.append('content', codeContent);
}
const response = await api.post('/submissions', formData);
setSubmission(response);
console.log('Submission successful');
} catch (error) {
console.error('Submission failed:', error);
} finally {
setSubmitting(false);
}
};
if (!assignment) return <div>加载中...</div>;
return (
<div className="submit-assignment">
<div className="assignment-detail">
<h1>{assignment.title}</h1>
<p className="description">{assignment.description}</p>
<div className="meta-info">
<span>📅 截止时间: {formatDate(assignment.dueDate)}</span>
<span>📝 类型: {assignment.type}</span>
<span>⭐ 总分: {assignment.rubric.reduce((sum, r) => sum + r.maxPoints, 0)} 分</span>
</div>
<div className="instructions">
<h3>作业要求</h3>
<pre>{assignment.instructions}</pre>
</div>
</div>
<form onSubmit={handleSubmit} className="submission-form">
<h2>提交作业</h2>
{assignment.type === 'coding' && (
<div className="code-editor">
<textarea
value={codeContent}
onChange={(e) => setCodeContent(e.target.value)}
placeholder="在这里粘贴你的代码"
rows="20"
/>
</div>
)}
{assignment.type === 'file_upload' && (
<div className="file-upload">
<input
type="file"
onChange={(e) => setFile(e.target.files[0])}
required
/>
</div>
)}
{submission && (
<div className="previous-submission">
<h3>上一次提交 (尝试 {submission.attempt} / {assignment.maxAttempts})</h3>
<p>提交时间: {formatDate(submission.submittedAt)}</p>
{submission.grade && (
<p>成绩: {submission.grade} / {assignment.rubric.reduce((sum, r) => sum + r.maxPoints, 0)}</p>
)}
</div>
)}
<button type="submit" disabled={submitting}>
{submitting ? '提交中...' : '提交作业'}
</button>
</form>
</div>
);
}
评分系统
教师评分界面
function GradeSubmission() {
const { submissionId } = useParams();
const [submission, setSubmission] = useState(null);
const [grades, setGrades] = useState({});
const [feedback, setFeedback] = useState('');
useEffect(() => {
fetchSubmission(submissionId).then(setSubmission);
}, [submissionId]);
const handleScoreChange = (rubricIndex, score) => {
setGrades({
...grades,
[rubricIndex]: score,
});
};
const totalScore = Object.values(grades).reduce((sum, score) => sum + score, 0);
const handleSubmitGrade = async () => {
try {
const gradeData = {
submissionId,
scores: grades,
totalScore,
feedback,
gradedAt: new Date().toISOString(),
};
await api.post(`/submissions/${submissionId}/grade`, gradeData);
console.log('Grade submitted');
} catch (error) {
console.error('Failed to submit grade:', error);
}
};
if (!submission) return <div>加载中...</div>;
return (
<div className="grade-submission">
<div className="submission-content">
<h2>{submission.assignment.title}</h2>
<p>学生: {submission.student.name}</p>
<p>提交时间: {formatDate(submission.submittedAt)}</p>
<div className="code-display">
<pre>{submission.content}</pre>
</div>
</div>
<div className="grading-panel">
<h3>评分</h3>
{submission.assignment.rubric.map((rubric, index) => (
<div key={index} className="rubric-grading">
<div className="rubric-header">
<span>{rubric.criterion}</span>
<span>最高分: {rubric.maxPoints}</span>
</div>
<input
type="range"
min="0"
max={rubric.maxPoints}
value={grades[index] || 0}
onChange={(e) => handleScoreChange(index, parseInt(e.target.value))}
className="score-slider"
/>
<span className="score-display">{grades[index] || 0} / {rubric.maxPoints}</span>
</div>
))}
<div className="total-score">
<h4>总分: {totalScore} / {submission.assignment.rubric.reduce((sum, r) => sum + r.maxPoints, 0)}</h4>
</div>
<textarea
placeholder="添加反馈意见"
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
rows="8"
/>
<button onClick={handleSubmitGrade}>提交评分</button>
</div>
</div>
);
}
批量管理
作业列表与操作
function AssignmentManagement() {
const [assignments, setAssignments] = useState([]);
const [filter, setFilter] = useState('all'); // all, pending, graded, overdue
useEffect(() => {
fetchAssignments().then(setAssignments);
}, []);
const filteredAssignments = assignments.filter(a => {
if (filter === 'pending') return a.submissions < a.totalStudents;
if (filter === 'graded') return a.gradedSubmissions === a.submissions;
if (filter === 'overdue') return new Date(a.dueDate) < new Date();
return true;
});
return (
<div className="assignment-management">
<div className="filter-bar">
<button
className={filter === 'all' ? 'active' : ''}
onClick={() => setFilter('all')}
>
全部 ({assignments.length})
</button>
<button
className={filter === 'pending' ? 'active' : ''}
onClick={() => setFilter('pending')}
>
待提交
</button>
<button
className={filter === 'graded' ? 'active' : ''}
onClick={() => setFilter('graded')}
>
已评分
</button>
<button
className={filter === 'overdue' ? 'active' : ''}
onClick={() => setFilter('overdue')}
>
已逾期
</button>
</div>
<table className="assignments-table">
<thead>
<tr>
<th>作业标题</th>
<th>截止时间</th>
<th>提交数</th>
<th>评分数</th>
<th>平均分</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{filteredAssignments.map(assignment => (
<tr key={assignment.id}>
<td>{assignment.title}</td>
<td>{formatDate(assignment.dueDate)}</td>
<td>{assignment.submissions} / {assignment.totalStudents}</td>
<td>{assignment.gradedSubmissions}</td>
<td>{assignment.averageScore?.toFixed(2) || '-'}</td>
<td>
<button>查看提交</button>
<button>评分</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
最佳实践
✅ 应该做的事:
- 清晰的作业要求
- 详细的评分标准
- 及时的反馈
- 允许多次尝试
- 提供资源和示例
❌ 不应该做的事:
- 不清楚的评分标准
- 即时反馈缺失
- 过于严格的限制
- 无法预览作业
- 忽视学生困难
检查清单
- 作业要求明确
- 评分标准详细
- 提交系统可靠
- 反馈及时
- 技术支持充分


