|
|
@@ -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>
|