feat: enhance media management features with pagination, category selection, and improved UI elements
This commit is contained in:
163
src/App.svelte
163
src/App.svelte
@@ -4,12 +4,88 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import request from './lib/request';
|
import request from './lib/request';
|
||||||
|
|
||||||
|
// 类型定义
|
||||||
|
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> {
|
||||||
|
code: number;
|
||||||
|
data: T;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageResponse {
|
||||||
|
list: Media[];
|
||||||
|
total: number;
|
||||||
|
currentPage: number;
|
||||||
|
pageSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const isAuthenticated = writable(false);
|
const isAuthenticated = writable(false);
|
||||||
const username = writable('');
|
const username = writable('');
|
||||||
const password = writable('');
|
const password = writable('');
|
||||||
const error = writable('');
|
const error = writable('');
|
||||||
|
|
||||||
|
// 媒体列表状态
|
||||||
|
const currentCategory = writable('game');
|
||||||
|
const mediaList = writable<Media[]>([]);
|
||||||
|
const currentPage = writable(1);
|
||||||
|
const pageSize = writable(10);
|
||||||
|
const totalItems = writable(0);
|
||||||
|
const loading = writable(false);
|
||||||
|
|
||||||
|
// 类别选项
|
||||||
|
const categories = [
|
||||||
|
{ id: 'game', label: 'Games' },
|
||||||
|
{ id: 'book', label: 'Books' },
|
||||||
|
{ id: 'movie', label: 'Movies' },
|
||||||
|
{ id: 'anime', label: 'Anime' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 获取媒体列表
|
||||||
|
async function fetchMediaList() {
|
||||||
|
loading.set(true);
|
||||||
|
try {
|
||||||
|
const response = await request.get<ApiResponse<PageResponse>>('/media/page', {
|
||||||
|
params: {
|
||||||
|
type: $currentCategory,
|
||||||
|
currentPage: $currentPage,
|
||||||
|
pageSize: $pageSize
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
mediaList.set(response.data.data.list);
|
||||||
|
totalItems.set(response.data.data.total);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
error.set(e.message || 'Failed to fetch media list');
|
||||||
|
} finally {
|
||||||
|
loading.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听类别变化
|
||||||
|
$: if ($isAuthenticated) {
|
||||||
|
currentPage.set(1);
|
||||||
|
fetchMediaList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听分页变化
|
||||||
|
$: if ($isAuthenticated && $currentPage) {
|
||||||
|
fetchMediaList();
|
||||||
|
}
|
||||||
|
|
||||||
// 登录处理
|
// 登录处理
|
||||||
async function handleLogin() {
|
async function handleLogin() {
|
||||||
try {
|
try {
|
||||||
@@ -17,15 +93,26 @@
|
|||||||
const auth = btoa(`${$username}:${$password}`);
|
const auth = btoa(`${$username}:${$password}`);
|
||||||
localStorage.setItem('auth', auth);
|
localStorage.setItem('auth', auth);
|
||||||
|
|
||||||
const response = await request.get('/media/list');
|
// 使用 page 接口验证登录
|
||||||
|
const response = await request.get<ApiResponse<PageResponse>>('/media/page', {
|
||||||
|
params: {
|
||||||
|
type: 'game',
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 10
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(response)
|
||||||
if (response.code === 0) {
|
if (response.code === 0) {
|
||||||
isAuthenticated.set(true);
|
isAuthenticated.set(true);
|
||||||
error.set('');
|
error.set('');
|
||||||
|
// 设置初始数据
|
||||||
|
mediaList.set(response.data.list);
|
||||||
|
totalItems.set(response.data.total);
|
||||||
} else {
|
} else {
|
||||||
error.set(response.message || 'Invalid username or password');
|
error.set(response.message || 'Invalid username or password');
|
||||||
localStorage.removeItem('auth');
|
localStorage.removeItem('auth');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
error.set(e.message || 'Connection error');
|
error.set(e.message || 'Connection error');
|
||||||
localStorage.removeItem('auth');
|
localStorage.removeItem('auth');
|
||||||
}
|
}
|
||||||
@@ -94,8 +181,76 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="min-h-screen bg-gray-100" transition:fade>
|
<div class="min-h-screen bg-gray-100" transition:fade>
|
||||||
<!-- 这里将添加主内容页面 -->
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<h1>Welcome, {$username}!</h1>
|
<!-- 类别选择栏 -->
|
||||||
|
<div class="bg-white shadow rounded-lg mb-6">
|
||||||
|
<div class="px-4 py-5 sm:p-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'}"
|
||||||
|
on:click={() => currentCategory.set(category.id)}
|
||||||
|
>
|
||||||
|
{category.label}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 媒体列表 -->
|
||||||
|
<div class="bg-white shadow rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
{#if $loading}
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600 mx-auto"></div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="space-y-4">
|
||||||
|
{#each $mediaList as media}
|
||||||
|
<div class="border rounded-lg p-4 hover:bg-gray-50">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">{media.title}</h3>
|
||||||
|
<p class="text-sm text-gray-500">Status: {media.status}</p>
|
||||||
|
{#if media.rating}
|
||||||
|
<p class="text-sm text-gray-500">Rating: {media.rating}/10</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
{media.date ? new Date(media.date).toLocaleDateString() : 'No date'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页控制 -->
|
||||||
|
<div class="mt-4 flex justify-between items-center">
|
||||||
|
<div class="text-sm text-gray-700">
|
||||||
|
Showing {($currentPage - 1) * $pageSize + 1} to {Math.min($currentPage * $pageSize, $totalItems)} of {$totalItems} items
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<button
|
||||||
|
class="px-3 py-1 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'}"
|
||||||
|
disabled={$currentPage === 1}
|
||||||
|
on:click={() => currentPage.set($currentPage - 1)}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="px-3 py-1 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'}"
|
||||||
|
disabled={$currentPage * $pageSize >= $totalItems}
|
||||||
|
on:click={() => currentPage.set($currentPage + 1)}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* @Date: 2025-05-20 17:59:45
|
||||||
|
* @LastEditors: 陈子健
|
||||||
|
* @LastEditTime: 2025-05-20 18:04:34
|
||||||
|
* @FilePath: /my-score/frontend/vite.config.ts
|
||||||
|
*/
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user