refactor: replace writable stores with reactive variables, update media category labels to Chinese, and streamline event handling
This commit is contained in:
110
src/App.svelte
110
src/App.svelte
@@ -1,7 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AppBar, AppRail, AppRailTile } from '@skeletonlabs/skeleton';
|
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
import request from './lib/request';
|
import request from './lib/request';
|
||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
@@ -32,65 +30,71 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const isAuthenticated = writable(false);
|
let isAuthenticated = $state(false);
|
||||||
const username = writable('');
|
let username = $state('');
|
||||||
const password = writable('');
|
let password = $state('');
|
||||||
const error = writable('');
|
let error = $state('');
|
||||||
|
|
||||||
// 媒体列表状态
|
// 媒体列表状态
|
||||||
const currentCategory = writable('game');
|
let currentCategory = $state('game');
|
||||||
const mediaList = writable<Media[]>([]);
|
let mediaList = $state<Media[]>([]);
|
||||||
const currentPage = writable(1);
|
let currentPage = $state(1);
|
||||||
const pageSize = writable(10);
|
let pageSize = $state(10);
|
||||||
const totalItems = writable(0);
|
let totalItems = $state(0);
|
||||||
const loading = writable(false);
|
let loading = $state(false);
|
||||||
|
|
||||||
// 类别选项
|
// 类别选项
|
||||||
const categories = [
|
const categories = [
|
||||||
{ id: 'game', label: 'Games' },
|
{ id: 'game', label: '游戏' },
|
||||||
{ id: 'book', label: 'Books' },
|
{ id: 'book', label: '书籍' },
|
||||||
{ id: 'movie', label: 'Movies' },
|
{ id: 'movie', label: '电影' },
|
||||||
{ id: 'anime', label: 'Anime' }
|
{ id: 'anime', label: '番剧' },
|
||||||
|
{ id: 'other', label: '其他' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// 获取媒体列表
|
// 获取媒体列表
|
||||||
async function fetchMediaList() {
|
async function fetchMediaList() {
|
||||||
loading.set(true);
|
loading = true;
|
||||||
try {
|
try {
|
||||||
const response = await request.get<ApiResponse<PageResponse>>('/media/page', {
|
const response = await request.get<ApiResponse<PageResponse>>('/media/page', {
|
||||||
params: {
|
params: {
|
||||||
type: $currentCategory,
|
type: currentCategory,
|
||||||
currentPage: $currentPage,
|
currentPage: currentPage,
|
||||||
pageSize: $pageSize
|
pageSize: pageSize
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
mediaList.set(response.data.data.list);
|
mediaList = response.data.data.list;
|
||||||
totalItems.set(response.data.data.total);
|
totalItems = response.data.data.total;
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
error.set(e.message || 'Failed to fetch media list');
|
error = e.message || 'Failed to fetch media list';
|
||||||
} finally {
|
} finally {
|
||||||
loading.set(false);
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听类别变化
|
// 监听类别变化
|
||||||
$: if ($isAuthenticated) {
|
$effect(() => {
|
||||||
currentPage.set(1);
|
if (isAuthenticated && currentCategory) {
|
||||||
|
currentPage = 1;
|
||||||
fetchMediaList();
|
fetchMediaList();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 监听分页变化
|
// 监听分页变化
|
||||||
$: if ($isAuthenticated && $currentPage) {
|
$effect(() => {
|
||||||
|
if (isAuthenticated && currentPage) {
|
||||||
fetchMediaList();
|
fetchMediaList();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 登录处理
|
// 登录处理
|
||||||
async function handleLogin() {
|
async function handleLogin(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
// 保存认证信息
|
// 保存认证信息
|
||||||
const auth = btoa(`${$username}:${$password}`);
|
const auth = btoa(`${username}:${password}`);
|
||||||
localStorage.setItem('auth', auth);
|
localStorage.setItem('auth', auth);
|
||||||
|
|
||||||
// 使用 page 接口验证登录
|
// 使用 page 接口验证登录
|
||||||
@@ -103,17 +107,17 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
isAuthenticated.set(true);
|
isAuthenticated = true;
|
||||||
error.set('');
|
error = '';
|
||||||
// 设置初始数据
|
// 设置初始数据
|
||||||
mediaList.set(response.data.data.list);
|
mediaList = response.data.data.list;
|
||||||
totalItems.set(response.data.data.total);
|
totalItems = response.data.data.total;
|
||||||
} else {
|
} else {
|
||||||
error.set(response.data.message || 'Invalid username or password');
|
error = response.data.message || 'Invalid username or password';
|
||||||
localStorage.removeItem('auth');
|
localStorage.removeItem('auth');
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
error.set(e.message || 'Connection error');
|
error = e.message || 'Connection error';
|
||||||
localStorage.removeItem('auth');
|
localStorage.removeItem('auth');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +127,7 @@
|
|||||||
<title>My Score</title>
|
<title>My Score</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{#if !$isAuthenticated}
|
{#if !isAuthenticated}
|
||||||
<div class="min-h-screen flex items-center justify-center bg-gray-100" transition:fade>
|
<div class="min-h-screen flex items-center justify-center bg-gray-100" transition:fade>
|
||||||
<div class="w-[480px] space-y-8 p-8 bg-white rounded-lg shadow-lg">
|
<div class="w-[480px] space-y-8 p-8 bg-white rounded-lg shadow-lg">
|
||||||
<div>
|
<div>
|
||||||
@@ -134,7 +138,7 @@
|
|||||||
Sign in to your account
|
Sign in to your account
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form class="mt-8 space-y-6" on:submit|preventDefault={handleLogin}>
|
<form class="mt-8 space-y-6" onsubmit={handleLogin}>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="username" class="block text-sm font-medium text-gray-700">Username</label>
|
<label for="username" class="block text-sm font-medium text-gray-700">Username</label>
|
||||||
@@ -145,7 +149,7 @@
|
|||||||
required
|
required
|
||||||
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||||
placeholder="Enter your username"
|
placeholder="Enter your username"
|
||||||
bind:value={$username}
|
bind:value={username}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -157,14 +161,14 @@
|
|||||||
required
|
required
|
||||||
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||||
placeholder="Enter your password"
|
placeholder="Enter your password"
|
||||||
bind:value={$password}
|
bind:value={password}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $error}
|
{#if error}
|
||||||
<div class="text-red-500 text-sm text-center">
|
<div class="text-red-500 text-sm text-center">
|
||||||
{$error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -188,8 +192,8 @@
|
|||||||
<div class="flex space-x-6">
|
<div class="flex space-x-6">
|
||||||
{#each categories as category}
|
{#each categories as category}
|
||||||
<button
|
<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-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)}
|
onclick={() => currentCategory = category.id}
|
||||||
>
|
>
|
||||||
{category.label}
|
{category.label}
|
||||||
</button>
|
</button>
|
||||||
@@ -201,14 +205,14 @@
|
|||||||
<!-- 媒体列表 -->
|
<!-- 媒体列表 -->
|
||||||
<div class="bg-white shadow rounded-lg">
|
<div class="bg-white shadow rounded-lg">
|
||||||
<div class="px-6 py-4">
|
<div class="px-6 py-4">
|
||||||
{#if $loading}
|
{#if loading}
|
||||||
<div class="text-center py-8">
|
<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 class="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600 mx-auto"></div>
|
||||||
</div>
|
</div>
|
||||||
{: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-6 hover:bg-gray-50">
|
<div class="border rounded-lg p-3 hover:bg-gray-50">
|
||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<h3 class="text-lg font-medium text-gray-900">{media.title}</h3>
|
<h3 class="text-lg font-medium text-gray-900">{media.title}</h3>
|
||||||
@@ -233,20 +237,20 @@
|
|||||||
<!-- 分页控制 -->
|
<!-- 分页控制 -->
|
||||||
<div class="mt-6 flex justify-between items-center">
|
<div class="mt-6 flex justify-between items-center">
|
||||||
<div class="text-sm text-gray-700">
|
<div class="text-sm text-gray-700">
|
||||||
Showing {($currentPage - 1) * $pageSize + 1} to {Math.min($currentPage * $pageSize, $totalItems)} of {$totalItems} items
|
Showing {(currentPage - 1) * pageSize + 1} to {Math.min(currentPage * pageSize, totalItems)} of {totalItems} items
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<button
|
<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-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}
|
disabled={currentPage === 1}
|
||||||
on:click={() => currentPage.set($currentPage - 1)}
|
onclick={() => currentPage = currentPage - 1}
|
||||||
>
|
>
|
||||||
Previous
|
Previous
|
||||||
</button>
|
</button>
|
||||||
<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-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}
|
disabled={currentPage * pageSize >= totalItems}
|
||||||
on:click={() => currentPage.set($currentPage + 1)}
|
onclick={() => currentPage = currentPage + 1}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user