feat: modularize media display by creating MediaItem component, define Media interface, and simplify media list rendering
This commit is contained in:
@@ -1,20 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import request from './lib/request';
|
import request from './lib/request';
|
||||||
|
import type { Media } from './lib/interfaces';
|
||||||
|
import MediaItem from './lib/MediaItem.svelte';
|
||||||
// 类型定义
|
// 类型定义
|
||||||
interface Media {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
type: string;
|
|
||||||
status: string;
|
|
||||||
rating?: number;
|
|
||||||
notes?: string;
|
|
||||||
platform?: string;
|
|
||||||
date?: string;
|
|
||||||
created_at: string;
|
|
||||||
updated_at: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
code: number;
|
code: number;
|
||||||
@@ -76,15 +65,14 @@
|
|||||||
|
|
||||||
// 监听类别变化
|
// 监听类别变化
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (isAuthenticated && currentCategory) {
|
if (currentCategory) {
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
fetchMediaList();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听分页变化
|
// 监听分页变化
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (isAuthenticated && currentPage) {
|
if (currentPage) {
|
||||||
fetchMediaList();
|
fetchMediaList();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -212,25 +200,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="grid grid-cols-2 gap-6">
|
<div class="grid grid-cols-2 gap-6">
|
||||||
{#each mediaList as media}
|
{#each mediaList as media}
|
||||||
<div class="border rounded-lg p-3 hover:bg-gray-50">
|
<MediaItem {media} />
|
||||||
<div class="flex justify-between items-start">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900">{media.title}</h3>
|
|
||||||
<div class="flex items-center space-x-4 text-sm text-gray-500">
|
|
||||||
<span class="px-2 py-1 bg-gray-100 rounded">Status: {media.status}</span>
|
|
||||||
{#if media.rating}
|
|
||||||
<span class="px-2 py-1 bg-gray-100 rounded">Rating: {media.rating}/10</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if media.platform}
|
|
||||||
<p class="text-sm text-gray-500">Platform: {media.platform}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="text-sm text-gray-500">
|
|
||||||
{media.date ? new Date(media.date).toLocaleDateString() : 'No date'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
69
src/lib/MediaItem.svelte
Normal file
69
src/lib/MediaItem.svelte
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Media } from './interfaces';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
|
export let media: Media;
|
||||||
|
|
||||||
|
// 状态映射
|
||||||
|
const statusMap = {
|
||||||
|
'completed': '已完成',
|
||||||
|
'in_progress': '进行中',
|
||||||
|
'plan_to_watch': '计划中'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 星星评分组件
|
||||||
|
function StarRating({ rating }: { rating: number }) {
|
||||||
|
const maxStars = 5;
|
||||||
|
const stars = [];
|
||||||
|
|
||||||
|
for (let i = 1; i <= maxStars; i++) {
|
||||||
|
const fill = i <= rating ? '#FFD700' : '#E5E7EB';
|
||||||
|
stars.push(`
|
||||||
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="${fill}" stroke="currentColor">
|
||||||
|
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
|
||||||
|
</svg>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stars.join('');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="border rounded-lg p-4 hover:bg-gray-50" transition:fade>
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="space-y-2 flex-1">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 truncate max-w-[70%]">{media.title}</h3>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
{media.date ? new Date(media.date).toLocaleDateString() : '未设置日期'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<span class="px-2 py-1 bg-gray-100 rounded text-sm text-gray-700">
|
||||||
|
状态:{statusMap[media.status] || media.status}
|
||||||
|
</span>
|
||||||
|
{#if media.rating}
|
||||||
|
<div class="px-2 py-1 bg-gray-100 rounded text-sm text-gray-700 flex items-center gap-1">
|
||||||
|
<span>评分:</span>
|
||||||
|
<div class="flex items-center">
|
||||||
|
{@html StarRating({ rating: media.rating / 2 })}
|
||||||
|
</div>
|
||||||
|
<span class="ml-1">({media.rating}/10)</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if media.platform}
|
||||||
|
<span class="px-2 py-1 bg-gray-100 rounded text-sm text-gray-700">
|
||||||
|
平台:{media.platform}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if media.notes}
|
||||||
|
<div class="mt-2 text-sm text-gray-600 bg-gray-50 p-3 rounded">
|
||||||
|
<div class="whitespace-pre-wrap text-left line-clamp-1">{media.notes}</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
12
src/lib/interfaces.ts
Normal file
12
src/lib/interfaces.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export interface Media {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
status: 'completed' | 'in_progress' | 'plan_to_watch';
|
||||||
|
rating?: number;
|
||||||
|
notes?: string;
|
||||||
|
platform?: string;
|
||||||
|
date?: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user