361 lines
12 KiB
TypeScript
361 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useParams, useRouter } from 'next/navigation';
|
|
import { Book } from '@/lib/types';
|
|
import { ArrowLeft, Save } from 'lucide-react';
|
|
|
|
export default function EditBookPage() {
|
|
const params = useParams();
|
|
const router = useRouter();
|
|
const bookId = params.id as string;
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const [formData, setFormData] = useState({
|
|
isbn: '',
|
|
title: '',
|
|
authors: '',
|
|
publisher: '',
|
|
publishDate: '',
|
|
price: '',
|
|
classificationNo: '',
|
|
location: '',
|
|
totalCopies: '1',
|
|
availableCopies: '1',
|
|
status: 'normal',
|
|
description: '',
|
|
coverUrl: '',
|
|
});
|
|
|
|
useEffect(() => {
|
|
const fetchBook = async () => {
|
|
try {
|
|
const response = await fetch(`/api/books/${bookId}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch book');
|
|
}
|
|
const book: Book = await response.json();
|
|
|
|
setFormData({
|
|
isbn: book.isbn,
|
|
title: book.title,
|
|
authors: book.authors.join(', '),
|
|
publisher: book.publisher || '',
|
|
publishDate: book.publishDate || '',
|
|
price: book.price || '',
|
|
classificationNo: book.classificationNo || '',
|
|
location: book.location || '',
|
|
totalCopies: book.totalCopies.toString(),
|
|
availableCopies: book.availableCopies.toString(),
|
|
status: book.status,
|
|
description: book.description || '',
|
|
coverUrl: book.coverUrl || '',
|
|
});
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
if (bookId) {
|
|
fetchBook();
|
|
}
|
|
}, [bookId]);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setSaving(true);
|
|
|
|
try {
|
|
const updateData = {
|
|
...formData,
|
|
authors: formData.authors.split(',').map(author => author.trim()).filter(Boolean),
|
|
price: formData.price ? parseFloat(formData.price) : null,
|
|
totalCopies: parseInt(formData.totalCopies),
|
|
availableCopies: parseInt(formData.availableCopies),
|
|
publishDate: formData.publishDate || null,
|
|
publisher: formData.publisher || null,
|
|
classificationNo: formData.classificationNo || null,
|
|
location: formData.location || null,
|
|
description: formData.description || null,
|
|
coverUrl: formData.coverUrl || null,
|
|
};
|
|
|
|
const response = await fetch(`/api/books/${bookId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(updateData),
|
|
});
|
|
|
|
if (response.ok) {
|
|
router.push(`/admin/books/${bookId}`);
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`更新失败: ${errorData.error || 'Unknown error'}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Update error:', error);
|
|
alert('更新失败');
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
|
const { name, value } = e.target;
|
|
setFormData(prev => ({
|
|
...prev,
|
|
[name]: value
|
|
}));
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
<div className="text-lg">加载中...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
<div className="text-red-600">错误: {error}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50">
|
|
<div className="max-w-4xl mx-auto p-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-6">
|
|
<div className="flex items-center gap-4">
|
|
<button
|
|
onClick={() => router.back()}
|
|
className="flex items-center gap-2 text-gray-600 hover:text-gray-900"
|
|
>
|
|
<ArrowLeft size={20} />
|
|
返回
|
|
</button>
|
|
<h1 className="text-2xl font-bold text-gray-900">编辑图书</h1>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Form */}
|
|
<div className="bg-white rounded-lg shadow-sm border p-6">
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
ISBN *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="isbn"
|
|
value={formData.isbn}
|
|
onChange={handleChange}
|
|
required
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
书名 *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="title"
|
|
value={formData.title}
|
|
onChange={handleChange}
|
|
required
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
作者 * (多个作者用逗号分隔)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="authors"
|
|
value={formData.authors}
|
|
onChange={handleChange}
|
|
required
|
|
placeholder="张三, 李四"
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
出版社
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="publisher"
|
|
value={formData.publisher}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
出版日期
|
|
</label>
|
|
<input
|
|
type="date"
|
|
name="publishDate"
|
|
value={formData.publishDate}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
价格
|
|
</label>
|
|
<input
|
|
type="number"
|
|
name="price"
|
|
value={formData.price}
|
|
onChange={handleChange}
|
|
step="0.01"
|
|
min="0"
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
分类号
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="classificationNo"
|
|
value={formData.classificationNo}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
位置
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="location"
|
|
value={formData.location}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
总册数 *
|
|
</label>
|
|
<input
|
|
type="number"
|
|
name="totalCopies"
|
|
value={formData.totalCopies}
|
|
onChange={handleChange}
|
|
required
|
|
min="1"
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
可借册数 *
|
|
</label>
|
|
<input
|
|
type="number"
|
|
name="availableCopies"
|
|
value={formData.availableCopies}
|
|
onChange={handleChange}
|
|
required
|
|
min="0"
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
状态 *
|
|
</label>
|
|
<select
|
|
name="status"
|
|
value={formData.status}
|
|
onChange={handleChange}
|
|
required
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
>
|
|
<option value="normal">正常</option>
|
|
<option value="damaged">损坏</option>
|
|
<option value="removed">已移除</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
封面链接
|
|
</label>
|
|
<input
|
|
type="url"
|
|
name="coverUrl"
|
|
value={formData.coverUrl}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
描述
|
|
</label>
|
|
<textarea
|
|
name="description"
|
|
value={formData.description}
|
|
onChange={handleChange}
|
|
rows={4}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-4">
|
|
<button
|
|
type="button"
|
|
onClick={() => router.back()}
|
|
className="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50"
|
|
>
|
|
取消
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={saving}
|
|
className="flex items-center gap-2 px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
|
>
|
|
<Save size={16} />
|
|
{saving ? '保存中...' : '保存'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|