331 lines
12 KiB
TypeScript
331 lines
12 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import { Plus, Edit, Trash2, Search, Save, X } from 'lucide-react'
|
||
import { ResourceCategoryService, ResourceCategory } from '../services/resourceCategoryService'
|
||
|
||
const ResourceCategoryPage: React.FC = () => {
|
||
const [categories, setCategories] = useState<ResourceCategory[]>([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [searchTerm, setSearchTerm] = useState('')
|
||
const [editingCategory, setEditingCategory] = useState<ResourceCategory | null>(null)
|
||
const [showCreateForm, setShowCreateForm] = useState(false)
|
||
const [formData, setFormData] = useState({
|
||
title: '',
|
||
ai_prompt: '',
|
||
color: '#FF6B6B'
|
||
})
|
||
|
||
// 预设颜色选项
|
||
const presetColors = [
|
||
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD',
|
||
'#FF7675', '#74B9FF', '#00B894', '#FDCB6E', '#E17055', '#A29BFE',
|
||
'#FD79A8', '#6C5CE7', '#00CEC9', '#55A3FF', '#FF9F43', '#26DE81'
|
||
]
|
||
|
||
useEffect(() => {
|
||
loadCategories()
|
||
}, [])
|
||
|
||
const loadCategories = async () => {
|
||
try {
|
||
setLoading(true)
|
||
const response = await ResourceCategoryService.getAllCategories()
|
||
console.log(`loadCategories`, response)
|
||
if (response.status && response.data) {
|
||
setCategories(response.data)
|
||
} else {
|
||
console.error('Failed to load categories:', response.msg)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to load categories:', error)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleCreateCategory = async () => {
|
||
try {
|
||
const response = await ResourceCategoryService.createCategory(formData)
|
||
console.log(`handleCreateCategory`, response)
|
||
if (response.status && response.data) {
|
||
setCategories([...categories, response.data])
|
||
setShowCreateForm(false)
|
||
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B' })
|
||
} else {
|
||
console.error('创建失败:', response.msg || '未知错误')
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to create category:', error)
|
||
}
|
||
}
|
||
|
||
const handleUpdateCategory = async () => {
|
||
if (!editingCategory) return
|
||
|
||
try {
|
||
const response = await ResourceCategoryService.updateCategory(editingCategory.id, formData)
|
||
if (response.status && response.data) {
|
||
const updatedCategories = categories.map(cat =>
|
||
cat.id === editingCategory.id ? response.data! : cat
|
||
)
|
||
setCategories(updatedCategories)
|
||
setEditingCategory(null)
|
||
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B' })
|
||
} else {
|
||
console.error('更新失败:', response.msg || '未知错误')
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to update category:', error)
|
||
}
|
||
}
|
||
|
||
const handleDeleteCategory = async (categoryId: string) => {
|
||
if (!confirm('确定要删除这个分类吗?')) return
|
||
|
||
try {
|
||
const response = await ResourceCategoryService.deleteCategory(categoryId)
|
||
if (response.status) {
|
||
setCategories(categories.filter(cat => cat.id !== categoryId))
|
||
} else {
|
||
console.error('删除失败:', response.msg || '未知错误')
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to delete category:', error)
|
||
}
|
||
}
|
||
|
||
const startEdit = (category: ResourceCategory) => {
|
||
setEditingCategory(category)
|
||
setFormData({
|
||
title: category.title,
|
||
ai_prompt: category.ai_prompt,
|
||
color: category.color
|
||
})
|
||
setShowCreateForm(false)
|
||
}
|
||
|
||
const cancelEdit = () => {
|
||
setEditingCategory(null)
|
||
setShowCreateForm(false)
|
||
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B' })
|
||
}
|
||
|
||
const filteredCategories = categories.filter(category =>
|
||
category.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||
category.ai_prompt.toLowerCase().includes(searchTerm.toLowerCase())
|
||
)
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="flex items-center justify-center h-64">
|
||
<div className="text-gray-500">加载中...</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="p-6">
|
||
{/* 页面标题 */}
|
||
<div className="mb-6">
|
||
<h1 className="text-2xl font-bold text-gray-900">素材分类管理</h1>
|
||
<p className="text-gray-600 mt-2">管理素材分类,设置AI识别提示词和展示颜色</p>
|
||
</div>
|
||
|
||
{/* 搜索和创建按钮 */}
|
||
<div className="flex items-center justify-between mb-6">
|
||
<div className="relative flex-1 max-w-md">
|
||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
|
||
<input
|
||
type="text"
|
||
placeholder="搜索分类..."
|
||
value={searchTerm}
|
||
onChange={(e) => setSearchTerm(e.target.value)}
|
||
className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent w-full"
|
||
/>
|
||
</div>
|
||
|
||
<button
|
||
onClick={() => {
|
||
setShowCreateForm(true)
|
||
setEditingCategory(null)
|
||
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B' })
|
||
}}
|
||
className="ml-4 flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||
>
|
||
<Plus size={20} className="mr-2" />
|
||
新建分类
|
||
</button>
|
||
</div>
|
||
|
||
{/* 分类列表 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
{filteredCategories.map((category) => (
|
||
<div key={category.id} className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||
{/* 分类标题和颜色 */}
|
||
<div className="flex items-center justify-between mb-4">
|
||
<div className="flex items-center">
|
||
<div
|
||
className="w-4 h-4 rounded-full mr-3"
|
||
style={{ backgroundColor: category.color }}
|
||
/>
|
||
<h3 className="text-lg font-semibold text-gray-900">{category.title}</h3>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2">
|
||
<button
|
||
onClick={() => startEdit(category)}
|
||
className="p-2 text-gray-400 hover:text-blue-600 transition-colors"
|
||
title="编辑"
|
||
>
|
||
<Edit size={16} />
|
||
</button>
|
||
<button
|
||
onClick={() => handleDeleteCategory(category.id)}
|
||
className="p-2 text-gray-400 hover:text-red-600 transition-colors"
|
||
title="删除"
|
||
>
|
||
<Trash2 size={16} />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* AI提示词 */}
|
||
<div className="mb-4">
|
||
<p className="text-sm text-gray-600 leading-relaxed">{category.ai_prompt}</p>
|
||
</div>
|
||
|
||
{/* 创建时间 */}
|
||
<div className="text-xs text-gray-400">
|
||
创建于 {new Date(category.created_at).toLocaleDateString()}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 空状态 */}
|
||
{filteredCategories.length === 0 && (
|
||
<div className="text-center py-12">
|
||
<div className="text-gray-400 mb-4">
|
||
{searchTerm ? '没有找到匹配的分类' : '暂无分类'}
|
||
</div>
|
||
{!searchTerm && (
|
||
<button
|
||
onClick={() => setShowCreateForm(true)}
|
||
className="text-blue-600 hover:text-blue-700"
|
||
>
|
||
创建第一个分类
|
||
</button>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* 创建/编辑表单弹窗 */}
|
||
{(showCreateForm || editingCategory) && (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div className="bg-white rounded-lg shadow-xl w-full max-w-md mx-4">
|
||
{/* 表单标题 */}
|
||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||
<h2 className="text-lg font-semibold text-gray-900">
|
||
{editingCategory ? '编辑分类' : '新建分类'}
|
||
</h2>
|
||
<button
|
||
onClick={cancelEdit}
|
||
className="text-gray-400 hover:text-gray-600"
|
||
>
|
||
<X size={24} />
|
||
</button>
|
||
</div>
|
||
|
||
{/* 表单内容 */}
|
||
<div className="p-6 space-y-4">
|
||
{/* 分类标题 */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
分类标题
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={formData.title}
|
||
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||
placeholder="请输入分类标题"
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
/>
|
||
</div>
|
||
|
||
{/* AI识别提示词 */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
AI识别提示词
|
||
</label>
|
||
<textarea
|
||
value={formData.ai_prompt}
|
||
onChange={(e) => setFormData({ ...formData, ai_prompt: e.target.value })}
|
||
placeholder="描述这类素材的特征,用于AI自动分类"
|
||
rows={3}
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||
/>
|
||
</div>
|
||
|
||
{/* 展示颜色 */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
展示颜色
|
||
</label>
|
||
<div className="flex items-center space-x-3 mb-3">
|
||
<div
|
||
className="w-8 h-8 rounded-lg border-2 border-gray-300"
|
||
style={{ backgroundColor: formData.color }}
|
||
/>
|
||
<input
|
||
type="color"
|
||
value={formData.color}
|
||
onChange={(e) => setFormData({ ...formData, color: e.target.value })}
|
||
className="w-16 h-8 border border-gray-300 rounded cursor-pointer"
|
||
/>
|
||
<span className="text-sm text-gray-600">{formData.color}</span>
|
||
</div>
|
||
|
||
{/* 预设颜色 */}
|
||
<div className="grid grid-cols-9 gap-2">
|
||
{presetColors.map((color) => (
|
||
<button
|
||
key={color}
|
||
onClick={() => setFormData({ ...formData, color })}
|
||
className={`w-8 h-8 rounded-lg border-2 transition-all ${
|
||
formData.color === color
|
||
? 'border-gray-800 scale-110'
|
||
: 'border-gray-300 hover:border-gray-400'
|
||
}`}
|
||
style={{ backgroundColor: color }}
|
||
title={color}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 表单按钮 */}
|
||
<div className="flex items-center justify-end space-x-3 p-6 border-t border-gray-200">
|
||
<button
|
||
onClick={cancelEdit}
|
||
className="px-4 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
onClick={editingCategory ? handleUpdateCategory : handleCreateCategory}
|
||
disabled={!formData.title.trim() || !formData.ai_prompt.trim()}
|
||
className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
|
||
>
|
||
<Save size={16} className="mr-2" />
|
||
{editingCategory ? '保存' : '创建'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default ResourceCategoryPage
|