refactor: 完成静态网站转换,移除所有 CMS 和动态功能

- 删除数据库相关代码 (src/db/)
- 删除 API 路由 (src/app/api/)
- 删除认证相关代码 (src/lib/auth/, src/providers/)
- 删除监控和安全中间件 (src/lib/security/, src/lib/monitoring/)
- 删除 hooks (use-news, use-products, use-services)
- 更新组件为静态数据源
- 添加 nginx 静态配置和部署脚本
- 添加 static-link 组件
This commit is contained in:
张翔
2026-04-21 07:53:56 +08:00
parent cd1d6aa28a
commit 6403489954
197 changed files with 654 additions and 24762 deletions
-422
View File
@@ -1,422 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import {
Users as UsersIcon,
Plus,
Edit,
Trash2,
Loader2,
Search
} from 'lucide-react';
interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'editor' | 'viewer';
createdAt: string;
}
const roleLabels = {
admin: '管理员',
editor: '编辑',
viewer: '查看者'
};
const roleColors = {
admin: 'bg-red-100 text-red-800',
editor: 'bg-blue-100 text-blue-800',
viewer: 'bg-gray-100 text-gray-800'
};
export default function UsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
const [showCreateModal, setShowCreateModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [saving, setSaving] = useState(false);
const [deletingUserId, setDeletingUserId] = useState<string | null>(null);
const [formData, setFormData] = useState({
email: '',
name: '',
password: '',
role: 'viewer' as 'admin' | 'editor' | 'viewer'
});
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
try {
setLoading(true);
const res = await fetch('/api/admin/users');
const data = await res.json();
if (res.ok) {
setUsers(data.users || []);
}
} catch (error) {
console.error('获取用户列表失败:', error);
} finally {
setLoading(false);
}
};
const handleCreate = async () => {
if (!formData.email || !formData.name || !formData.password || !formData.role) {
return;
}
try {
setSaving(true);
const res = await fetch('/api/admin/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (res.ok) {
setShowCreateModal(false);
setFormData({ email: '', name: '', password: '', role: 'viewer' });
await fetchUsers();
} else {
const data = await res.json();
alert(data.error || '创建失败');
}
} catch (error) {
console.error('创建用户失败:', error);
} finally {
setSaving(false);
}
};
const handleDelete = async (userId: string) => {
if (deletingUserId) {
console.log('删除操作正在进行中,请勿重复点击');
return;
}
if (!confirm('确定要删除此用户吗?此操作不可恢复。')) {
return;
}
try {
setDeletingUserId(userId);
const res = await fetch(`/api/admin/users/${userId}`, {
method: 'DELETE'
});
if (res.ok) {
await fetchUsers();
} else {
const data = await res.json();
alert(data.error || '删除失败');
}
} catch (error) {
console.error('删除用户失败:', error);
alert('删除失败,请稍后重试');
} finally {
setDeletingUserId(null);
}
};
const filteredUsers = users.filter(user =>
user.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-gray-400" />
</div>
);
}
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="text-gray-600 mt-1">管理系统用户和权限</p>
</div>
<button
onClick={() => setShowCreateModal(true)}
className="flex items-center gap-2 px-4 py-2 bg-[#C41E3A] text-white rounded-lg hover:bg-[#A01830] transition-colors"
>
<Plus className="h-4 w-4" />
添加用户
</button>
</div>
<div className="bg-white rounded-lg border">
<div className="p-4 border-b">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<input
type="text"
placeholder="搜索用户..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A] focus:border-transparent"
/>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
用户信息
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
角色
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
创建时间
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
操作
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredUsers.map(user => (
<tr key={user.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="w-10 h-10 bg-gray-200 rounded-full flex items-center justify-center">
<UsersIcon className="h-5 w-5 text-gray-600" />
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">{user.name}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`px-3 py-1 rounded-full text-xs font-medium ${roleColors[user.role]}`}>
{roleLabels[user.role]}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{new Date(user.createdAt).toLocaleDateString('zh-CN')}
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
onClick={(e) => {
e.stopPropagation();
setSelectedUser(user);
setFormData({
email: user.email,
name: user.name,
password: '',
role: user.role
});
setShowEditModal(true);
}}
className="text-[#C41E3A] hover:text-[#A01830] mr-4"
>
<Edit className="h-4 w-4 inline" />
</button>
<button
onClick={(e) => {
e.stopPropagation();
handleDelete(user.id);
}}
disabled={deletingUserId === user.id}
className="text-red-600 hover:text-red-800 disabled:opacity-50 disabled:cursor-not-allowed"
>
{deletingUserId === user.id ? (
<Loader2 className="h-4 w-4 inline animate-spin" />
) : (
<Trash2 className="h-4 w-4 inline" />
)}
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{filteredUsers.length === 0 && (
<div className="text-center py-12 text-gray-500">
暂无用户数据
</div>
)}
</div>
{/* Create Modal */}
{showCreateModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4">添加用户</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">邮箱</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">姓名</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">密码</label>
<input
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">角色</label>
<select
value={formData.role}
onChange={(e) => setFormData({ ...formData, role: e.target.value as any })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
>
<option value="viewer">查看者</option>
<option value="editor">编辑</option>
<option value="admin">管理员</option>
</select>
</div>
</div>
<div className="flex justify-end gap-3 mt-6">
<button
onClick={() => {
setShowCreateModal(false);
setFormData({ email: '', name: '', password: '', role: 'viewer' });
}}
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50"
>
取消
</button>
<button
onClick={handleCreate}
disabled={saving}
className="px-4 py-2 bg-[#C41E3A] text-white rounded-lg hover:bg-[#A01830] disabled:opacity-50"
>
{saving ? '创建中...' : '创建'}
</button>
</div>
</div>
</div>
)}
{/* Edit Modal */}
{showEditModal && selectedUser && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4">编辑用户</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">邮箱</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">姓名</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">新密码(留空则不修改)</label>
<input
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">角色</label>
<select
value={formData.role}
onChange={(e) => setFormData({ ...formData, role: e.target.value as any })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
>
<option value="viewer">查看者</option>
<option value="editor">编辑</option>
<option value="admin">管理员</option>
</select>
</div>
</div>
<div className="flex justify-end gap-3 mt-6">
<button
onClick={() => {
setShowEditModal(false);
setSelectedUser(null);
setFormData({ email: '', name: '', password: '', role: 'viewer' });
}}
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50"
>
取消
</button>
<button
onClick={async () => {
setSaving(true);
try {
const updateData: any = {
email: formData.email,
name: formData.name,
role: formData.role
};
if (formData.password) {
updateData.password = formData.password;
}
const res = await fetch(`/api/admin/users/${selectedUser.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updateData)
});
if (res.ok) {
setShowEditModal(false);
setSelectedUser(null);
setFormData({ email: '', name: '', password: '', role: 'viewer' });
await fetchUsers();
}
} catch (error) {
console.error('更新用户失败:', error);
} finally {
setSaving(false);
}
}}
disabled={saving}
className="px-4 py-2 bg-[#C41E3A] text-white rounded-lg hover:bg-[#A01830] disabled:opacity-50"
>
{saving ? '保存中...' : '保存'}
</button>
</div>
</div>
</div>
)}
</div>
);
}