StudentGrid.vue 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. <template>
  2. <div class="grid-4">
  3. <div
  4. v-for="s in students"
  5. :key="s.userId"
  6. class="student-card"
  7. :class="['status-' + s.status]"
  8. @click="$emit('click', s)"
  9. >
  10. <div class="avatar" :class="'avatar-' + s.status">
  11. {{ s.name.charAt(0) }}
  12. </div>
  13. <span class="name">{{ s.name }}</span>
  14. <span v-if="s.status === 'submitted'" class="indicator check">✓</span>
  15. <span v-else-if="s.status === 'unsubmitted'" class="indicator dot pulse" />
  16. <span v-else class="indicator dash">—</span>
  17. </div>
  18. </div>
  19. </template>
  20. <script lang="ts" setup>
  21. import type { ClassStudentSummary } from './types'
  22. defineProps<{ students: ClassStudentSummary[] }>()
  23. defineEmits<{ click: [student: ClassStudentSummary] }>()
  24. </script>
  25. <style lang="scss" scoped>
  26. .grid-4 {
  27. display: grid;
  28. grid-template-columns: repeat(4, minmax(0, 1fr));
  29. gap: 12px;
  30. }
  31. .student-card {
  32. display: flex;
  33. align-items: center;
  34. gap: 10px;
  35. padding: 8px 12px;
  36. border-radius: 8px;
  37. cursor: pointer;
  38. transition: box-shadow .15s ease;
  39. background: #fff;
  40. border: 2px solid transparent;
  41. &:hover { box-shadow: 0 2px 8px rgba(0,0,0,.08); }
  42. &.status-submitted { border-color: #facc15; }
  43. &.status-unsubmitted { border-color: #fde68a; }
  44. &.status-not_started {
  45. background: #f3f4f6;
  46. border: 2px dashed #9ca3af;
  47. }
  48. }
  49. .avatar {
  50. flex-shrink: 0;
  51. width: 32px; height: 32px;
  52. border-radius: 50%;
  53. display: flex;
  54. align-items: center;
  55. justify-content: center;
  56. font-size: 12px;
  57. font-weight: 600;
  58. }
  59. .avatar-submitted { background: #facc15; color: #111827; }
  60. .avatar-unsubmitted { background: #fef3c7; color: #92400e; }
  61. .avatar-not_started { background: #e5e7eb; color: #6b7280; }
  62. .name {
  63. flex: 1;
  64. font-size: 14px;
  65. font-weight: 500;
  66. color: #111827;
  67. overflow: hidden;
  68. text-overflow: ellipsis;
  69. white-space: nowrap;
  70. }
  71. .status-not_started .name { color: #9ca3af; }
  72. .indicator {
  73. flex-shrink: 0;
  74. font-size: 12px;
  75. }
  76. .indicator.check { color: #ca8a04; font-weight: 600; }
  77. .indicator.dash { color: #9ca3af; }
  78. .indicator.dot.pulse {
  79. width: 8px; height: 8px;
  80. border-radius: 50%;
  81. background: #fbbf24;
  82. animation: pulse 1.5s ease-in-out infinite;
  83. }
  84. @keyframes pulse {
  85. 0%, 100% { opacity: 1; }
  86. 50% { opacity: .4; }
  87. }
  88. </style>