作业系统与提交管理

AI Content Team
21 分钟阅读

深入了解在线教育平台的作业创建、分配、提交和评分系统

作业系统与提交管理

高效的作业系统是教学评估的关键。本文讲解完整的作业管理流程。

作业模型设计

作业数据结构

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>
  );
}

最佳实践

应该做的事:

  • 清晰的作业要求
  • 详细的评分标准
  • 及时的反馈
  • 允许多次尝试
  • 提供资源和示例

不应该做的事:

  • 不清楚的评分标准
  • 即时反馈缺失
  • 过于严格的限制
  • 无法预览作业
  • 忽视学生困难

检查清单

  • 作业要求明确
  • 评分标准详细
  • 提交系统可靠
  • 反馈及时
  • 技术支持充分