feat: enhance UI components with improved styling, transitions, and layout adjustments across App, MediaFormModal, MediaItem, and StarRating components

This commit is contained in:
ethan.chen
2025-06-03 17:41:10 +08:00
parent 701021c112
commit 35e0df26fe
5 changed files with 70 additions and 77 deletions

View File

@@ -273,16 +273,16 @@
</div>
</div>
{:else}
<div class="min-h-screen bg-gray-100" transition:fade>
<div class="min-w-[1200px] mx-auto px-6 py-8">
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100" transition:fade>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- 类别选择栏 -->
<div class="bg-white shadow rounded-lg mb-6">
<div class="bg-white/80 backdrop-blur-sm shadow-sm rounded-xl mb-6 border border-gray-200">
<div class="px-6 py-4">
<div class="flex justify-between items-center">
<div class="flex space-x-6">
<div class="flex space-x-4">
{#each categories as category}
<button
class="px-4 py-2 rounded-md text-sm font-medium {currentCategory === category.id ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}"
class="px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 {currentCategory === category.id ? 'bg-gradient-to-r from-indigo-500 to-indigo-600 text-white shadow-sm' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}"
onclick={() => currentCategory = category.id}
>
{category.label}
@@ -290,7 +290,7 @@
{/each}
</div>
<button
class="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
class="px-4 py-2 text-sm font-medium text-white bg-gradient-to-r from-indigo-500 to-indigo-600 rounded-lg hover:from-indigo-600 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-all duration-200 shadow-sm hover:shadow-md"
onclick={() => showCreateModal = true}
>
添加新作品
@@ -300,14 +300,14 @@
</div>
<!-- 媒体列表 -->
<div class="bg-white shadow rounded-lg">
<div class="bg-white/80 backdrop-blur-sm shadow-sm rounded-xl border border-gray-200">
<div class="px-6 py-4">
{#if loading}
<div class="text-center py-8">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600 mx-auto"></div>
</div>
{:else}
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
{#each mediaList as media}
<MediaItem {media} onEdit={handleEditClick} onDelete={handleDeleteClick} />
{/each}
@@ -315,19 +315,19 @@
<!-- 分页控制 -->
<div class="mt-6 flex justify-between items-center">
<div class="text-sm text-gray-700">
<div class="text-sm text-gray-600">
Showing {(currentPage - 1) * pageSize + 1} to {Math.min(currentPage * pageSize, totalItems)} of {totalItems} items
</div>
<div class="flex space-x-2">
<div class="flex space-x-3">
<button
class="px-4 py-2 rounded-md text-sm font-medium {currentPage === 1 ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white text-gray-700 hover:bg-gray-50 border'}"
class="px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 {currentPage === 1 ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white text-gray-700 hover:bg-gray-50 border border-gray-200 shadow-sm hover:shadow-md'}"
disabled={currentPage === 1}
onclick={() => currentPage = currentPage - 1}
>
Previous
</button>
<button
class="px-4 py-2 rounded-md text-sm font-medium {currentPage * pageSize >= totalItems ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white text-gray-700 hover:bg-gray-50 border'}"
class="px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 {currentPage * pageSize >= totalItems ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white text-gray-700 hover:bg-gray-50 border border-gray-200 shadow-sm hover:shadow-md'}"
disabled={currentPage * pageSize >= totalItems}
onclick={() => currentPage = currentPage + 1}
>

View File

@@ -50,23 +50,23 @@
{#if show}
<div
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
class="fixed inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center z-50"
transition:fade={{ duration: 200 }}
onclick={handleClose}
role="presentation"
>
<div
class="bg-white rounded-lg p-6 w-full mx-4 max-w-[800px]"
class="bg-gradient-to-br from-white to-gray-50 rounded-xl p-6 w-full mx-4 max-w-[800px] shadow-2xl"
transition:scale={{ duration: 200 }}
onclick={(e) => e.stopPropagation()}
role="presentation"
>
<div class="flex justify-between items-center mb-6">
<h2 id="modal-title" class="text-xl font-semibold text-gray-900">
<h2 id="modal-title" class="text-xl font-semibold bg-gradient-to-r from-gray-900 to-gray-600 bg-clip-text text-transparent">
{mode === 'add' ? '添加新作品' : '编辑作品'}
</h2>
<button
class="text-gray-400 hover:text-gray-500"
class="text-gray-400 hover:text-gray-600 transition-colors duration-200"
onclick={handleClose}
type="button"
aria-label="关闭"
@@ -84,7 +84,7 @@
id="title"
type="text"
bind:value={media.title}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white/50 backdrop-blur-sm transition-all duration-200"
placeholder="请输入标题"
required
aria-required="true"
@@ -96,7 +96,7 @@
<select
id="type"
bind:value={media.type}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white/50 backdrop-blur-sm transition-all duration-200"
required
aria-required="true"
>
@@ -131,7 +131,7 @@
id="platform"
type="text"
bind:value={media.platform}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white/50 backdrop-blur-sm transition-all duration-200"
placeholder="例如Steam、Netflix、Kindle等"
aria-label="输入平台名称"
/>
@@ -142,7 +142,7 @@
<textarea
id="notes"
bind:value={media.notes}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white/50 backdrop-blur-sm transition-all duration-200"
rows="10"
placeholder="添加一些备注..."
aria-label="添加备注信息"
@@ -152,7 +152,7 @@
<div class="flex justify-end gap-3 mt-6">
<button
type="button"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transition-all duration-200"
onclick={handleClose}
>
取消
@@ -160,7 +160,7 @@
<button
type="button"
onclick={() => submitMedia(media)}
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
class="px-4 py-2 text-sm font-medium text-white bg-gradient-to-r from-indigo-500 to-indigo-600 rounded-lg hover:from-indigo-600 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-all duration-200 shadow-sm hover:shadow-md"
>
{mode === 'add' ? '添加' : '保存'}
</button>

View File

@@ -1,66 +1,58 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import type { Media } from './interfaces';
let {media, onEdit, onDelete}: {media: Media, onEdit: (media: Media) => void, onDelete: (media: Media) => void} = $props();
// 状态映射
const statusMap = {
'completed': '已完成',
'in_progress': '进行中',
'plan_to_watch': '计划中'
};
// 星星评分组件
import { StarRating } from './utils';
</script>
<div class="border rounded-lg p-4 hover:bg-gray-50 relative" transition:fade>
<button
class="delete-button absolute top-0 right-0 p-2 text-gray-400/50 hover:text-red-500 hover:bg-red-50 rounded-full transition-all duration-200"
onclick={(e) => {
e.stopPropagation();
onDelete(media);
}}
aria-label="删除"
>
<svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</button>
<div class="flex justify-between items-start" role="presentation" onclick={() => onEdit(media)}>
<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%]">
<div class="bg-white/90 backdrop-blur-sm rounded-xl shadow-[0_2px_8px_-2px_rgba(0,0,0,0.1)] hover:shadow-[0_4px_12px_-4px_rgba(0,0,0,0.15)] transition-all duration-200 border border-gray-100/50 overflow-hidden">
<div class="p-6">
<div class="flex justify-between items-center mb-1">
<span>
<h2 class="text-lg font-semibold text-gray-800 line-clamp-1">
{media.title}
{#if media.platform}
<span class="px-2 py-1 bg-gray-100 rounded text-sm text-gray-700">
平台:{media.platform}
</span>
{/if}
</h3>
<div class="flex flex-wrap gap-2 ml-auto mr-2">
{#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(media.rating / 2)}
</div>
<span class="ml-1">({media.rating}/10)</span>
</div>
<span class="px-2 py-1 text-gray-600 text-sm bg-gray-50/80 rounded-full">{`(${media.platform})`}</span>
{/if}
</div>
<div class="text-sm text-gray-500">
{media.date ? new Date(media.date).toLocaleDateString() : '未设置日期'}
</div>
</h2>
</span>
<div class="flex items-center gap-2">
<button
onclick={() => onEdit(media)}
class="text-gray-400 hover:text-indigo-400 transition-colors duration-200"
aria-label="编辑"
>
<svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
<button
onclick={() => onDelete(media)}
class="text-gray-400 hover:text-rose-400 transition-colors duration-200"
aria-label="删除"
>
<svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</button>
</div>
{#if media.notes}
<div class="mt-2 text-sm text-gray-600 rounded">
<div class="whitespace-pre-wrap text-left line-clamp-4">{media.notes}</div>
</div>
{/if}
</div>
<div class="space-y-2 text-sm text-gray-600 flex justify-between">
<p class="flex items-center gap-2">
<span class="text-gray-500">评分:</span>
<span class="flex items-center gap-1">{@html StarRating(media.rating ?? 0)}</span>
</p>
<p class="flex items-center gap-2">
<span class="text-gray-500">日期:</span>
<span class="text-gray-700">{media.date}</span>
</p>
</div>
{#if media.notes}
<div class="mt-1 pt-1 border-t border-gray-100/50">
<p class="text-sm text-gray-600 line-clamp-3">{media.notes}</p>
</div>
{/if}
</div>
</div>

View File

@@ -18,9 +18,9 @@
<div class="flex items-center gap-1" onmouseleave={handleMouseLeave} role="presentation">
{#each Array(maxStars) as _, i}
<div
class="text-l focus:outline-none transition-colors duration-200 cursor-pointer"
class:text-yellow-400={i <(hoverValue || value)}
class:text-gray-300={i >= (hoverValue || value)}
class="text-xl focus:outline-none transition-all duration-200 cursor-pointer hover:scale-110"
class:text-yellow-400={i < (hoverValue || value)}
class:text-gray-200={i >= (hoverValue || value)}
onclick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -33,6 +33,6 @@
</div>
{/each}
{#if value > 0}
<span class="ml-2 text-sm text-gray-600">{value}</span>
<span class="ml-2 text-sm font-medium bg-gradient-to-r from-gray-900 to-gray-600 bg-clip-text text-transparent">{value}</span>
{/if}
</div>

View File

@@ -1,7 +1,7 @@
/*
* @Date: 2025-05-26 17:39:36
* @LastEditors: 陈子健
* @LastEditTime: 2025-05-26 17:39:47
* @LastEditTime: 2025-05-29 11:03:14
* @FilePath: /my-score/frontend/src/lib/utils.ts
*/
export const StarRating = (rating: number, maxStars: number = 5) => {
@@ -12,6 +12,7 @@ export const StarRating = (rating: number, maxStars: number = 5) => {
const isHalfStar = i - 0.5 <= rating && rating < i;
if (isHalfStar) {
console.log('isHalfStar', isHalfStar);
stars.push(`
<div class="relative w-4 h-4">
<svg class="absolute w-4 h-4" viewBox="0 0 24 24" fill="white" stroke="currentColor">