StudentGrid.vue 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. <template>
  2. <div class="grid-4">
  3. <div
  4. v-for="student in students"
  5. :key="student.userId"
  6. class="student-card"
  7. :class="'status-' + student.status"
  8. @click="$emit('click', student)"
  9. >
  10. <div class="avatar" :class="'avatar-' + student.status">
  11. {{ student.name.charAt(0) }}
  12. </div>
  13. <!-- 完成态与其余状态同形:只有名字,分数在个人报告弹窗里看(spec §8) -->
  14. <span class="name">{{ student.name }}</span>
  15. <span v-if="student.status === 'completed'" class="indicator check">✓</span>
  16. <span v-else-if="student.status === 'reading'" class="indicator dot pulse"></span>
  17. <span v-else class="indicator dash">—</span>
  18. </div>
  19. </div>
  20. </template>
  21. <script lang="ts" setup>
  22. import type { ArticleClassSessionSummary } from '@/types/articleReading'
  23. defineProps<{ students: ArticleClassSessionSummary[] }>()
  24. defineEmits<{ click: [student: ArticleClassSessionSummary] }>()
  25. </script>
  26. <style lang="scss" scoped>
  27. .grid-4 {
  28. display: grid;
  29. grid-template-columns: repeat(4, minmax(0, 1fr));
  30. gap: 12px;
  31. }
  32. .student-card {
  33. display: flex;
  34. align-items: center;
  35. gap: 10px;
  36. padding: 8px 12px;
  37. border-radius: 8px;
  38. cursor: pointer;
  39. transition: box-shadow 0.15s ease;
  40. background: #fff;
  41. border: 2px solid transparent;
  42. &:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); }
  43. &.status-completed { border-color: #facc15; }
  44. &.status-reading { border-color: #fde68a; }
  45. &.status-not_started {
  46. background: #f3f4f6;
  47. border: 2px dashed #9ca3af;
  48. }
  49. }
  50. .avatar {
  51. flex-shrink: 0;
  52. width: 32px;
  53. height: 32px;
  54. border-radius: 50%;
  55. display: flex;
  56. align-items: center;
  57. justify-content: center;
  58. font-size: 14px;
  59. font-weight: 600;
  60. color: #fff;
  61. background: #d1d5db;
  62. &.avatar-completed { background: #f59e0b; }
  63. &.avatar-reading { background: #fbbf24; }
  64. }
  65. .name {
  66. flex: 1;
  67. min-width: 0;
  68. font-size: 13px;
  69. color: #111827;
  70. overflow: hidden;
  71. text-overflow: ellipsis;
  72. white-space: nowrap;
  73. }
  74. .indicator {
  75. flex-shrink: 0;
  76. font-size: 12px;
  77. &.check { color: #16a34a; }
  78. &.dash { color: #9ca3af; }
  79. &.dot {
  80. width: 8px;
  81. height: 8px;
  82. border-radius: 50%;
  83. background: #fbbf24;
  84. }
  85. &.pulse { animation: article-class-pulse 1.4s ease-in-out infinite; }
  86. }
  87. @keyframes article-class-pulse {
  88. 0%, 100% { opacity: 1; }
  89. 50% { opacity: 0.35; }
  90. }
  91. </style>