162 lines
5.2 KiB
TypeScript
162 lines
5.2 KiB
TypeScript
|
|
import { useState } from 'react';
|
||
|
|
import { useDocuments, useCreateDocument, useDeleteDocument } from '@/hooks/useDocuments';
|
||
|
|
import { Button } from '@/components/Button';
|
||
|
|
import { Input } from '@/components/Input';
|
||
|
|
import { Card } from '@/components/Card';
|
||
|
|
import { Plus, Trash2, Search } from 'lucide-react';
|
||
|
|
import { useSearchDocuments } from '@/hooks/useDocuments';
|
||
|
|
import type { Document } from '@/types';
|
||
|
|
|
||
|
|
export default function DocumentsPage() {
|
||
|
|
const { data: documents } = useDocuments();
|
||
|
|
const createDocument = useCreateDocument();
|
||
|
|
const deleteDocument = useDeleteDocument();
|
||
|
|
const searchDocuments = useSearchDocuments();
|
||
|
|
const [showCreateForm, setShowCreateForm] = useState(false);
|
||
|
|
const [searchQuery, setSearchQuery] = useState('');
|
||
|
|
const [searchResults, setSearchResults] = useState<Document[]>([]);
|
||
|
|
|
||
|
|
const [formData, setFormData] = useState({
|
||
|
|
title: '',
|
||
|
|
content: '',
|
||
|
|
});
|
||
|
|
|
||
|
|
const handleCreate = async (e: React.FormEvent) => {
|
||
|
|
e.preventDefault();
|
||
|
|
try {
|
||
|
|
await createDocument.mutateAsync(formData);
|
||
|
|
setFormData({ title: '', content: '' });
|
||
|
|
setShowCreateForm(false);
|
||
|
|
} catch (err: any) {
|
||
|
|
alert(err.message || '创建失败');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleDelete = async (id: string) => {
|
||
|
|
if (confirm('确定要删除这个文档吗?')) {
|
||
|
|
try {
|
||
|
|
await deleteDocument.mutateAsync(id);
|
||
|
|
} catch (err: any) {
|
||
|
|
alert(err.message || '删除失败');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSearch = async (e: React.FormEvent) => {
|
||
|
|
e.preventDefault();
|
||
|
|
if (!searchQuery.trim()) {
|
||
|
|
setSearchResults([]);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
const results = await searchDocuments.mutateAsync(searchQuery);
|
||
|
|
setSearchResults(results);
|
||
|
|
} catch (err: any) {
|
||
|
|
alert(err.message || '搜索失败');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const displayDocuments = searchResults.length > 0 ? searchResults : documents;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<h1 className="text-2xl font-bold text-gray-900">文档管理</h1>
|
||
|
|
<p className="mt-1 text-sm text-gray-600">管理您的文档资料</p>
|
||
|
|
</div>
|
||
|
|
<Button onClick={() => setShowCreateForm(!showCreateForm)}>
|
||
|
|
<Plus className="mr-2 h-4 w-4" />
|
||
|
|
新建文档
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Search */}
|
||
|
|
<Card>
|
||
|
|
<form onSubmit={handleSearch} className="flex gap-3">
|
||
|
|
<Input
|
||
|
|
className="flex-1"
|
||
|
|
placeholder="搜索文档..."
|
||
|
|
value={searchQuery}
|
||
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||
|
|
/>
|
||
|
|
<Button type="submit" variant="secondary">
|
||
|
|
<Search className="mr-2 h-4 w-4" />
|
||
|
|
搜索
|
||
|
|
</Button>
|
||
|
|
</form>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* Create Form */}
|
||
|
|
{showCreateForm && (
|
||
|
|
<Card title="新建文档">
|
||
|
|
<form onSubmit={handleCreate} className="space-y-4">
|
||
|
|
<Input
|
||
|
|
label="标题"
|
||
|
|
value={formData.title}
|
||
|
|
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||
|
|
placeholder="文档标题"
|
||
|
|
/>
|
||
|
|
<div>
|
||
|
|
<label className="mb-2 block text-sm font-medium text-gray-700">
|
||
|
|
内容
|
||
|
|
</label>
|
||
|
|
<textarea
|
||
|
|
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
|
|
rows={6}
|
||
|
|
value={formData.content}
|
||
|
|
onChange={(e) => setFormData({ ...formData, content: e.target.value })}
|
||
|
|
placeholder="文档内容"
|
||
|
|
required
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div className="flex justify-end gap-3">
|
||
|
|
<Button
|
||
|
|
type="button"
|
||
|
|
variant="secondary"
|
||
|
|
onClick={() => setShowCreateForm(false)}
|
||
|
|
>
|
||
|
|
取消
|
||
|
|
</Button>
|
||
|
|
<Button type="submit" loading={createDocument.isPending}>
|
||
|
|
创建
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</form>
|
||
|
|
</Card>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Documents List */}
|
||
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||
|
|
{displayDocuments?.map((doc) => (
|
||
|
|
<Card key={doc.id} variant="bordered">
|
||
|
|
<div className="mb-3 flex items-start justify-between">
|
||
|
|
<h3 className="text-lg font-semibold text-gray-900">
|
||
|
|
{doc.title || '无标题'}
|
||
|
|
</h3>
|
||
|
|
<button
|
||
|
|
onClick={() => handleDelete(doc.id)}
|
||
|
|
className="text-gray-400 hover:text-red-600"
|
||
|
|
>
|
||
|
|
<Trash2 className="h-4 w-4" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<p className="mb-3 line-clamp-3 text-sm text-gray-600">
|
||
|
|
{doc.content}
|
||
|
|
</p>
|
||
|
|
<p className="text-xs text-gray-500">
|
||
|
|
{new Date(doc.created_at).toLocaleString()}
|
||
|
|
</p>
|
||
|
|
</Card>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{displayDocuments?.length === 0 && (
|
||
|
|
<Card variant="bordered">
|
||
|
|
<p className="text-center text-gray-500">暂无文档</p>
|
||
|
|
</Card>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|