McpNewsPage.vue
1 <script setup lang="ts"> 2 import { computed, ref, onMounted } from 'vue' 3 import type { StartupNewsType } from '@/types/startup' 4 5 const mcpNews = ref<StartupNewsType[]>([]) 6 7 onMounted((): void => { 8 mcpNews.value = window.startupApis?.get().news || [] 9 }) 10 11 const placeholderColor = computed(() => { 12 return (index: number) => { 13 const hue = (index * 137.508) % 360 // Evenly distribute hues using golden angle approximation 14 const saturation = 30 + Math.sin(index * 0.5) * 20 // 30%~50% saturation (soft pastel tones) 15 const lightness = 50 + Math.cos(index * 0.3) * 10 // 40%~60% lightness (avoids extreme brightness) 16 return `data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Crect width='10' height='6' fill='hsl(${hue}, ${saturation}%, ${lightness}%)'/%3E%3C/svg%3E` 17 } 18 }) 19 </script> 20 21 <template> 22 <v-data-iterator :items="mcpNews" :items-per-page="-1"> 23 <template #default="{ items }"> 24 <v-container class="pa-0" fluid> 25 <v-row density="compact"> 26 <v-col v-for="(item, index) in items" :key="item.raw.title" cols="auto" md="4"> 27 <v-card class="pb-3" border flat> 28 <v-img 29 class="ma-2" 30 rounded="lg" 31 :cover="item.raw.cover ?? true" 32 :src="item.raw.img" 33 :height="140" 34 :lazy-src="placeholderColor(index)" 35 > 36 <template #placeholder> 37 <div class="d-flex align-center justify-center fill-height"> 38 <v-progress-circular color="grey-lighten-4" indeterminate></v-progress-circular> 39 </div> 40 </template> 41 <template #error> 42 <v-img></v-img> 43 </template> 44 </v-img> 45 <v-divider></v-divider> 46 47 <v-list-item lines="two"> 48 <template #title> 49 <strong class="text-h6 mb-1">{{ item.raw.title }}</strong> 50 </template> 51 <template #subtitle> 52 <div class="subtitle-text">{{ item.raw.subtitle }}</div> 53 </template> 54 </v-list-item> 55 56 <div class="d-flex justify-space-between px-4"> 57 <div class="d-flex align-center text-caption text-medium-emphasis me-1"> 58 <v-icon icon="mdi-clock" start></v-icon> 59 60 <div class="text-truncate">{{ `${item.raw.duration} ` + $t('mcp.minutes') }}</div> 61 </div> 62 63 <v-btn 64 class="text-none" 65 size="small" 66 :text="$t('mcp.read')" 67 variant="flat" 68 border 69 :href="item.raw.link" 70 target="_blank" 71 > 72 </v-btn> 73 </div> 74 </v-card> 75 </v-col> 76 </v-row> 77 </v-container> 78 </template> 79 </v-data-iterator> 80 </template> 81 82 <style scoped> 83 .subtitle-text { 84 padding-bottom: 2px; 85 } 86 </style>