Przeglądaj źródła

feat(speaking): add VideoSceneGrid 2x4 component

jimmylee 1 miesiąc temu
rodzic
commit
d3459255e3

+ 130 - 0
src/views/Editor/EnglishSpeaking/preview/VideoSceneGrid.vue

@@ -0,0 +1,130 @@
+<template>
+  <div class="vsg-grid">
+    <div
+      v-for="v in videos"
+      :key="v.id"
+      class="vsg-card"
+      :class="{ selected: selectedId === v.id }"
+    >
+      <div class="vsg-title">{{ v.title }}</div>
+      <div class="vsg-actions">
+        <button
+          class="vsg-radio"
+          :class="{ active: selectedId === v.id }"
+          @click="$emit('select', v)"
+          aria-label="选择"
+        >
+          <span v-if="selectedId === v.id" class="vsg-radio-dot" />
+        </button>
+        <button class="vsg-play" @click="$emit('play', v)" aria-label="播放">
+          <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
+            <polygon points="5 3 19 12 5 21 5 3" />
+          </svg>
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import type { VideoMeta } from '@/types/englishSpeaking'
+
+defineProps<{
+  videos: VideoMeta[]
+  selectedId: string | null
+}>()
+defineEmits<{
+  (e: 'select', v: VideoMeta): void
+  (e: 'play', v: VideoMeta): void
+}>()
+</script>
+
+<style lang="scss" scoped>
+.vsg-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  grid-template-rows: repeat(2, auto);
+  gap: 12px;
+  width: 100%;
+  max-width: 720px;
+  margin: 0 auto;
+}
+
+.vsg-card {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  background: #f9fafb;
+  border: 1px solid #e5e7eb;
+  border-radius: 10px;
+  padding: 10px;
+  min-height: 84px;
+  transition: border-color 0.15s, background 0.15s;
+
+  &.selected {
+    border-color: #f97316;
+    background: #fff7ed;
+  }
+}
+
+.vsg-title {
+  font-size: 13px;
+  font-weight: 500;
+  color: #1f2937;
+  line-height: 1.35;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.vsg-actions {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 8px;
+}
+
+.vsg-radio {
+  width: 18px;
+  height: 18px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 50%;
+  border: 2px solid #d1d5db;
+  background: #fff;
+  cursor: pointer;
+  padding: 0;
+  transition: border-color 0.15s;
+
+  &.active {
+    border-color: #f97316;
+  }
+}
+
+.vsg-radio-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background: #f97316;
+}
+
+.vsg-play {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 24px;
+  height: 24px;
+  border: none;
+  border-radius: 6px;
+  background: #f97316;
+  color: #fff;
+  cursor: pointer;
+
+  &:hover {
+    background: #ea580c;
+  }
+}
+</style>