家长互动与沟通系统

AI Content Team
19 分钟阅读

设计有效的家长门户和师生家长沟通平台

家长互动与沟通系统

有效的家校沟通能显著提升学生学习成果。本文讲解家长参与系统的设计。

家长门户设计

家长可见的信息

const parentPortalFeatures = {
  studentProgress: {
    currentGrades: true,
    gradeHistory: true,
    coursePerformance: true,
    compareToClassAverage: true,
  },
  
  assignments: {
    upcomingAssignments: true,
    dueDate: true,
    assignmentDescription: true,
    submissionStatus: true,
  },
  
  attendance: {
    presentAbsent: true,
    tardyRecord: true,
    attendanceTrend: true,
  },
  
  communication: {
    teacherMessages: true,
    schoolAnnouncements: true,
    eventNotifications: true,
  },
  
  parentControls: {
    notifications: true,
    informationVisibility: true,
    communicationPreferences: true,
  },
};

const parentAccount = {
  id: 'PARENT_001',
  name: '张三',
  email: 'parent@example.com',
  phone: '13800000000',
  linkedStudents: [
    {
      studentId: 'STU_001',
      name: '张小明',
      relationship: '父亲', // 父亲, 母亲, 监护人
      permissions: ['view_all'], // view_all, view_limited, view_grades_only
    },
  ],
  notificationSettings: {
    emailNotifications: true,
    pushNotifications: true,
    smsNotifications: false,
    notificationFrequency: 'daily', // immediate, daily, weekly
  },
  language: 'zh',
  timezone: 'Asia/Shanghai',
};

家长门户界面

完整的家长仪表板

function ParentPortal() {
  const [parent, setParent] = useState(null);
  const [students, setStudents] = useState([]);
  const [selectedStudent, setSelectedStudent] = useState(null);
  const [studentProgress, setStudentProgress] = useState(null);
  
  useEffect(() => {
    fetchParentInfo().then(setParent);
    fetchLinkedStudents().then(setStudents);
  }, []);
  
  useEffect(() => {
    if (selectedStudent) {
      fetchStudentProgress(selectedStudent.id).then(setStudentProgress);
    }
  }, [selectedStudent]);
  
  return (
    <div className="parent-portal">
      <header className="portal-header">
        <h1>家长门户</h1>
        <p>欢迎, {parent?.name}</p>
      </header>
      
      <div className="student-selector">
        <label>选择学生:</label>
        <select 
          value={selectedStudent?.id || ''}
          onChange={(e) => {
            const student = students.find(s => s.id === e.target.value);
            setSelectedStudent(student);
          }}
        >
          <option value="">-- 选择学生 --</option>
          {students.map(student => (
            <option key={student.id} value={student.id}>
              {student.name}
            </option>
          ))}
        </select>
      </div>
      
      {selectedStudent && studentProgress && (
        <>
          <section className="student-overview">
            <div className="overview-card">
              <h2>学生概览</h2>
              <div className="info-grid">
                <div className="info-item">
                  <span>学生名字</span>
                  <strong>{selectedStudent.name}</strong>
                </div>
                <div className="info-item">
                  <span>班级</span>
                  <strong>{selectedStudent.className}</strong>
                </div>
                <div className="info-item">
                  <span>班主任</span>
                  <strong>{selectedStudent.classTeacher}</strong>
                </div>
              </div>
            </div>
          </section>
          
          <section className="academic-performance">
            <h2>学习成绩</h2>
            
            <div className="performance-summary">
              <div className="summary-card">
                <h3>平均成绩</h3>
                <div className="grade-value">{studentProgress.averageGrade.toFixed(1)}</div>
                <p>班级平均: {studentProgress.classAverage.toFixed(1)}</p>
              </div>
              
              <div className="summary-card">
                <h3>排名</h3>
                <div className="rank-value">{studentProgress.rank} / {studentProgress.classSize}</div>
                <p>排名百分比: {studentProgress.percentile}%</p>
              </div>
            </div>
            
            <div className="courses-detail">
              {studentProgress.courses.map(course => (
                <div key={course.id} className="course-card">
                  <h4>{course.name}</h4>
                  <div className="grade-bar">
                    <div
                      className="grade-fill"
                      style={{width: `${(course.grade / 100) * 100}%`}}
                    >
                      {course.grade}
                    </div>
                  </div>
                  <div className="course-meta">
                    <span>教师: {course.teacher}</span>
                    <span>座位号: {course.seatNumber}</span>
                  </div>
                </div>
              ))}
            </div>
          </section>
          
          <section className="assignments-section">
            <h2>作业</h2>
            
            <div className="assignment-filter">
              <button className="filter-btn active">全部</button>
              <button className="filter-btn">待提交</button>
              <button className="filter-btn">已提交</button>
              <button className="filter-btn">逾期</button>
            </div>
            
            {studentProgress.assignments.map(assignment => (
              <div key={assignment.id} className="assignment-item">
                <div className="assignment-header">
                  <h4>{assignment.title}</h4>
                  <span className={`status status-${assignment.status}`}>
                    {assignment.status === 'submitted' ? '已提交' : '待提交'}
                  </span>
                </div>
                <p>{assignment.description}</p>
                <div className="assignment-meta">
                  <span>📅 截止时间: {formatDate(assignment.dueDate)}</span>
                  {assignment.status === 'graded' && (
                    <span>📝 成绩: {assignment.grade}</span>
                  )}
                </div>
              </div>
            ))}
          </section>
          
          <section className="attendance-section">
            <h2>出勤</h2>
            
            <div className="attendance-summary">
              <div className="attendance-stat">
                <h4>出勤率</h4>
                <div className="percentage">{studentProgress.attendanceRate}%</div>
              </div>
              
              <div className="attendance-stat">
                <h4>缺席</h4>
                <div className="count">{studentProgress.absenceCount}</div>
              </div>
              
              <div className="attendance-stat">
                <h4>迟到</h4>
                <div className="count">{studentProgress.tardyCount}</div>
              </div>
            </div>
            
            <div className="attendance-chart">
              <AttendanceChart data={studentProgress.attendanceData} />
            </div>
          </section>
        </>
      )}
    </div>
  );
}

沟通系统

家长-教师沟通

function ParentTeacherMessaging() {
  const [conversations, setConversations] = useState([]);
  const [selectedConversation, setSelectedConversation] = useState(null);
  const [messageText, setMessageText] = useState('');
  
  useEffect(() => {
    fetchConversations().then(setConversations);
  }, []);
  
  const handleSendMessage = async (e) => {
    e.preventDefault();
    
    if (!messageText.trim()) return;
    
    try {
      const message = {
        conversationId: selectedConversation.id,
        senderType: 'parent',
        content: messageText,
        timestamp: new Date().toISOString(),
      };
      
      await api.post('/messages', message);
      setMessageText('');
      
      // 刷新对话
      const updated = await fetchConversations();
      setConversations(updated);
    } catch (error) {
      console.error('Failed to send message:', error);
    }
  };
  
  const startNewConversation = async (teacherId) => {
    try {
      const conversation = await api.post('/conversations', {
        participants: ['parent_id', teacherId],
        subject: '关于我的孩子',
      });
      
      setConversations([...conversations, conversation]);
      setSelectedConversation(conversation);
    } catch (error) {
      console.error('Failed to start conversation:', error);
    }
  };
  
  return (
    <div className="messaging-interface">
      <div className="conversation-list">
        <h2>消息</h2>
        
        <div className="new-conversation">
          <button onClick={() => startNewConversation()}>
            + 新建对话
          </button>
        </div>
        
        {conversations.map(conv => (
          <div
            key={conv.id}
            className={`conversation-item ${selectedConversation?.id === conv.id ? 'active' : ''}`}
            onClick={() => setSelectedConversation(conv)}
          >
            <div className="conv-header">
              <h4>{conv.participantName}</h4>
              <span className="timestamp">{formatTime(conv.lastMessageTime)}</span>
            </div>
            <p className="conv-preview">{conv.lastMessage}</p>
            {conv.unreadCount > 0 && (
              <span className="unread-badge">{conv.unreadCount}</span>
            )}
          </div>
        ))}
      </div>
      
      {selectedConversation && (
        <div className="conversation-view">
          <div className="conversation-header">
            <h2>{selectedConversation.participantName}</h2>
            <p>关于: {selectedConversation.studentName}</p>
          </div>
          
          <div className="messages-container">
            {selectedConversation.messages.map(msg => (
              <div
                key={msg.id}
                className={`message ${msg.senderType === 'parent' ? 'sent' : 'received'}`}
              >
                <div className="message-content">
                  <p>{msg.content}</p>
                  {msg.attachments && msg.attachments.map(att => (
                    <div key={att.id} className="attachment">
                      📎 {att.name}
                    </div>
                  ))}
                </div>
                <span className="message-time">{formatTime(msg.timestamp)}</span>
              </div>
            ))}
          </div>
          
          <form onSubmit={handleSendMessage} className="message-form">
            <textarea
              value={messageText}
              onChange={(e) => setMessageText(e.target.value)}
              placeholder="输入你的消息..."
              rows="3"
            />
            <button type="submit">发送</button>
          </form>
        </div>
      )}
    </div>
  );
}

通知系统

推送通知与提醒

class ParentNotificationManager {
  constructor(parentId) {
    this.parentId = parentId;
    this.notificationTypes = {
      GRADE_UPDATE: '成绩更新',
      ASSIGNMENT_DUE: '作业截止提醒',
      ATTENDANCE_ALERT: '出勤异常警报',
      BEHAVIOR_REPORT: '行为报告',
      EVENT_REMINDER: '活动提醒',
      MESSAGE_RECEIVED: '收到新消息',
    };
  }
  
  // 创建通知
  async createNotification(type, data) {
    const notification = {
      parentId: this.parentId,
      type,
      title: this.getNotificationTitle(type, data),
      message: this.formatNotificationMessage(type, data),
      data,
      createdAt: new Date().toISOString(),
      read: false,
    };
    
    // 保存到数据库
    await api.post('/notifications', notification);
    
    // 发送推送通知
    await this.sendPushNotification(notification);
    
    return notification;
  }
  
  getNotificationTitle(type, data) {
    const titles = {
      GRADE_UPDATE: `${data.studentName} 新成绩`,
      ASSIGNMENT_DUE: `${data.assignmentTitle} 即将截止`,
      ATTENDANCE_ALERT: `${data.studentName} 出勤异常`,
      BEHAVIOR_REPORT: `${data.studentName} 行为报告`,
    };
    
    return titles[type] || '新通知';
  }
  
  formatNotificationMessage(type, data) {
    const messages = {
      GRADE_UPDATE: `${data.courseName} 新成绩: ${data.grade}`,
      ASSIGNMENT_DUE: `${data.assignmentTitle} 将在 ${formatDate(data.dueDate)} 截止`,
      ATTENDANCE_ALERT: `${data.studentName} 已缺席 ${data.absenceCount} 天`,
      BEHAVIOR_REPORT: `有新的行为反馈需要查看`,
    };
    
    return messages[type] || '点击查看详情';
  }
  
  // 获取未读通知
  async getUnreadNotifications() {
    const response = await api.get(`/parents/${this.parentId}/notifications?read=false`);
    return response.data;
  }
  
  // 标记为已读
  async markAsRead(notificationId) {
    await api.put(`/notifications/${notificationId}`, { read: true });
  }
  
  // 发送推送通知
  async sendPushNotification(notification) {
    try {
      // 如果父母启用了推送通知
      if (await this.isPushEnabled()) {
        // 使用推送服务
        // 例如: Firebase Cloud Messaging
        await pushService.send({
          title: notification.title,
          body: notification.message,
          data: { notificationId: notification.id },
        });
      }
    } catch (error) {
      console.error('Failed to send push notification:', error);
    }
  }
  
  async isPushEnabled() {
    const parent = await api.get(`/parents/${this.parentId}`);
    return parent.data.notificationSettings.pushNotifications;
  }
}

学校公告系统

学校级别的沟通

function SchoolAnnouncements() {
  const [announcements, setAnnouncements] = useState([]);
  const [filter, setFilter] = useState('all');
  
  useEffect(() => {
    fetchAnnouncements().then(setAnnouncements);
  }, []);
  
  const filteredAnnouncements = announcements.filter(a => {
    if (filter === 'important') return a.priority === 'high';
    if (filter === 'events') return a.category === 'event';
    if (filter === 'academic') return a.category === 'academic';
    return true;
  });
  
  return (
    <div className="announcements-section">
      <h2>学校公告</h2>
      
      <div className="filter-tabs">
        <button
          className={filter === 'all' ? 'active' : ''}
          onClick={() => setFilter('all')}
        >
          全部公告
        </button>
        <button
          className={filter === 'important' ? 'active' : ''}
          onClick={() => setFilter('important')}
        >
          重要通知
        </button>
        <button
          className={filter === 'events' ? 'active' : ''}
          onClick={() => setFilter('events')}
        >
          活动
        </button>
      </div>
      
      <div className="announcements-list">
        {filteredAnnouncements.map(announcement => (
          <div key={announcement.id} className="announcement-card">
            <div className="announcement-header">
              <h3>{announcement.title}</h3>
              {announcement.priority === 'high' && (
                <span className="priority-badge">重要</span>
              )}
              <span className="date">{formatDate(announcement.date)}</span>
            </div>
            
            <p className="announcement-content">{announcement.content}</p>
            
            {announcement.attachments && (
              <div className="attachments">
                {announcement.attachments.map(att => (
                  <a key={att.id} href={att.url} className="attachment-link">
                    📎 {att.name}
                  </a>
                ))}
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

最佳实践

应该做的事:

  • 定期的进度报告
  • 及时的沟通渠道
  • 清晰透明的信息
  • 尊重隐私
  • 易于理解的内容

不应该做的事:

  • 过度共享学生信息
  • 缺乏沟通机制
  • 模糊的成绩报告
  • 忽视家长反馈
  • 过于复杂的界面

检查清单

  • 门户信息准确
  • 通知及时
  • 沟通畅通
  • 隐私受保护
  • 易于使用