feat: enhance UI components with improved styling, transitions, and layout adjustments across App, MediaFormModal, MediaItem, and StarRating components
This commit is contained in:
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user