import React, { useState, useEffect } from 'react';
import { Plus, Search, Book, Bell, Gavel, Trash2, Edit3, X } from 'lucide-react';
interface Document {
id: number;
title: string;
content: string;
type: 'wiki' | 'rule' | 'notification';
tags: string;
updatedAt: string;
}
export default function App() {
const [docs, setDocs] = useState<Document[]>([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [editingDoc, setEditingDoc] = useState<Partial<Document> | null>(null);
useEffect(() => {
fetchDocs();
}, []);
const fetchDocs = async () => {
const res = await fetch('/api/documents');
const data = await res.json();
setDocs(data);
};
const handleSave = async (e: React.FormEvent) => {
e.preventDefault();
const method = editingDoc?.id ? 'PUT' : 'POST';
const url = editingDoc?.id ? `/api/documents/${editingDoc.id}` : '/api/documents';
await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(editingDoc),
});
setIsModalOpen(false);
setEditingDoc(null);
fetchDocs();
};
const handleDelete = async (id: number) => {
if (confirm('确定要删除这条内容吗?')) {
await fetch(`/api/documents/${id}`, { method: 'DELETE' });
fetchDocs();
}
};
const filteredDocs = docs.filter(d =>
d.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
d.content.toLowerCase().includes(searchQuery.toLowerCase())
);
return (
<div className="min-h-screen bg-[#f8fafc]">
{/* Sidebar / Header */}
<header className="bg-white border-b border-slate-200 sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center text-white font-bold">Y</div>
<h1 className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-indigo-600 to-violet-600">
YYSK MCP CMS
</h1>
</div>
<div className="flex items-center gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 w-4 h-4" />
<input
type="text"
placeholder="搜索内容..."
className="pl-10 pr-4 py-2 bg-slate-100 border-none rounded-full text-sm focus:ring-2 focus:ring-indigo-500 w-64 transition-all"
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
/>
</div>
<button
onClick={() => { setEditingDoc({ type: 'wiki' }); setIsModalOpen(true); }}
className="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors shadow-sm"
>
<Plus className="w-4 h-4" /> 新增内容
</button>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
{[
{ label: '内部 Wiki', count: docs.filter(d => d.type === 'wiki').length, icon: Book, color: 'text-blue-600', bg: 'bg-blue-50' },
{ label: '规章制度', count: docs.filter(d => d.type === 'rule').length, icon: Gavel, color: 'text-purple-600', bg: 'bg-purple-50' },
{ label: '重要通知', count: docs.filter(d => d.type === 'notification').length, icon: Bell, color: 'text-orange-600', bg: 'bg-orange-50' },
].map(stat => (
<div key={stat.label} className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm flex items-center gap-4">
<div className={`${stat.bg} ${stat.color} p-3 rounded-xl`}>
<stat.icon className="w-6 h-6" />
</div>
<div>
<p className="text-sm text-slate-500 font-medium">{stat.label}</p>
<p className="text-2xl font-bold text-slate-900">{stat.count}</p>
</div>
</div>
))}
</div>
{/* Content Table */}
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50 border-b border-slate-200">
<th className="px-6 py-4 text-xs font-semibold text-slate-500 uppercase tracking-wider">标题</th>
<th className="px-6 py-4 text-xs font-semibold text-slate-500 uppercase tracking-wider">类型</th>
<th className="px-6 py-4 text-xs font-semibold text-slate-500 uppercase tracking-wider">最后更新</th>
<th className="px-6 py-4 text-xs font-semibold text-slate-500 uppercase tracking-wider text-right">操作</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{filteredDocs.map(doc => (
<tr key={doc.id} className="hover:bg-slate-50 transition-colors">
<td className="px-6 py-4">
<div className="font-medium text-slate-900">{doc.title}</div>
<div className="text-sm text-slate-500 truncate max-w-md">{doc.content.substring(0, 60)}...</div>
</td>
<td className="px-6 py-4">
<span className={`px-2.5 py-0.5 rounded-full text-xs font-medium ${
doc.type === 'wiki' ? 'bg-blue-100 text-blue-700' :
doc.type === 'rule' ? 'bg-purple-100 text-purple-700' :
'bg-orange-100 text-orange-700'
}`}>
{doc.type === 'wiki' ? 'Wiki' : doc.type === 'rule' ? '制度' : '通知'}
</span>
</td>
<td className="px-6 py-4 text-sm text-slate-500">
{new Date(doc.updatedAt).toLocaleDateString()}
</td>
<td className="px-6 py-4 text-right">
<div className="flex justify-end gap-2">
<button
onClick={() => { setEditingDoc(doc); setIsModalOpen(true); }}
className="p-2 text-slate-400 hover:text-indigo-600 transition-colors"
>
<Edit3 className="w-4 h-4" />
</button>
<button
onClick={() => handleDelete(doc.id)}
className="p-2 text-slate-400 hover:text-red-500 transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
{filteredDocs.length === 0 && (
<tr>
<td colSpan={4} className="px-6 py-12 text-center text-slate-500">
未找到相关文档
</td>
</tr>
)}
</tbody>
</table>
</div>
</main>
{/* Editor Modal */}
{isModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/40 backdrop-blur-sm">
<div className="bg-white rounded-2xl shadow-xl w-full max-w-2xl overflow-hidden animate-in fade-in zoom-in duration-200">
<div className="px-6 py-4 border-b border-slate-100 flex items-center justify-between">
<h3 className="text-lg font-bold text-slate-900">
{editingDoc?.id ? '编辑内容' : '新增内容'}
</h3>
<button onClick={() => setIsModalOpen(false)} className="text-slate-400 hover:text-slate-600">
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleSave} className="p-6 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-xs font-semibold text-slate-500 uppercase">标题</label>
<input
required
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none transition-all"
value={editingDoc?.title || ''}
onChange={e => setEditingDoc({...editingDoc, title: e.target.value})}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-slate-500 uppercase">类型</label>
<select
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none transition-all"
value={editingDoc?.type || 'wiki'}
onChange={e => setEditingDoc({...editingDoc, type: e.target.value as any})}
>
<option value="wiki">内部 Wiki</option>
<option value="rule">规章制度</option>
<option value="notification">重要通知</option>
</select>
</div>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-slate-500 uppercase">内容 (支持 Markdown)</label>
<textarea
required
rows={8}
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none transition-all resize-none"
value={editingDoc?.content || ''}
onChange={e => setEditingDoc({...editingDoc, content: e.target.value})}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-slate-500 uppercase">标签 (逗号分隔)</label>
<input
placeholder="例如: 行政, 财务, 2024"
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none transition-all"
value={editingDoc?.tags || ''}
onChange={e => setEditingDoc({...editingDoc, tags: e.target.value})}
/>
</div>
<div className="pt-4 flex justify-end gap-3">
<button
type="button"
onClick={() => setIsModalOpen(false)}
className="px-4 py-2 text-sm font-medium text-slate-600 hover:bg-slate-50 rounded-lg transition-colors"
>
取消
</button>
<button
type="submit"
className="px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-lg transition-colors shadow-md"
>
保存
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
}