694 lines
25 KiB
TypeScript
694 lines
25 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import { Plus, Edit, Trash2, Search, Save, X, Cloud, Users, Palette, BarChart3, Download, Upload } from 'lucide-react'
|
||
import {
|
||
ResourceCategoryServiceV2,
|
||
ResourceCategoryV2,
|
||
CategoryBatchItem
|
||
} from '../services/resourceCategoryServiceV2'
|
||
import {
|
||
useCategoriesData,
|
||
useCategoryActions,
|
||
useCategoryStore
|
||
} from '../stores/useCategoryStore'
|
||
|
||
const ResourceCategoryPageV2: React.FC = () => {
|
||
// 使用 Zustand store 管理分类数据
|
||
const { categories, loading } = useCategoriesData()
|
||
const {
|
||
loadCategories,
|
||
refreshCategories,
|
||
addCategory,
|
||
updateCategory,
|
||
removeCategory
|
||
} = useCategoryActions()
|
||
|
||
// 从 store 获取搜索功能
|
||
const { searchCategories } = useCategoryStore()
|
||
|
||
// 本地 UI 状态
|
||
const [searchTerm, setSearchTerm] = useState('')
|
||
const [editingCategory, setEditingCategory] = useState<ResourceCategoryV2 | null>(null)
|
||
const [showCreateForm, setShowCreateForm] = useState(false)
|
||
const [showBatchImport, setShowBatchImport] = useState(false)
|
||
const [includeCloud, setIncludeCloud] = useState(true)
|
||
const [showDisabled, setShowDisabled] = useState(true)
|
||
const [selectedColor, setSelectedColor] = useState<string>('')
|
||
const [formData, setFormData] = useState({
|
||
title: '',
|
||
ai_prompt: '',
|
||
color: '#FF6B6B',
|
||
is_cloud: false
|
||
})
|
||
const [batchData, setBatchData] = useState('')
|
||
|
||
// 预设颜色选项(扩展版)
|
||
const presetColors = [
|
||
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD',
|
||
'#FF7675', '#74B9FF', '#00B894', '#FDCB6E', '#E17055', '#A29BFE',
|
||
'#FD79A8', '#6C5CE7', '#00CEC9', '#55A3FF', '#FF9F43', '#26DE81',
|
||
'#FF3838', '#FF9500', '#FFDD00', '#48CAE4', '#9B59B6', '#E74C3C',
|
||
'#F39C12', '#27AE60', '#3498DB', '#8E44AD', '#34495E', '#95A5A6'
|
||
]
|
||
|
||
useEffect(() => {
|
||
// 使用 store 的 loadCategories 方法
|
||
loadCategories()
|
||
}, [loadCategories])
|
||
|
||
// 获取过滤后的分类列表
|
||
const getFilteredCategories = () => {
|
||
let filtered = categories
|
||
|
||
// 根据搜索词过滤
|
||
if (searchTerm.trim()) {
|
||
filtered = searchCategories(searchTerm.trim())
|
||
}
|
||
|
||
// 根据云端/本地过滤
|
||
if (!includeCloud) {
|
||
filtered = filtered.filter(cat => !cat.is_cloud)
|
||
}
|
||
|
||
// 根据启用/禁用状态过滤
|
||
if (!showDisabled) {
|
||
filtered = filtered.filter(cat => cat.is_active)
|
||
}
|
||
|
||
// 根据颜色过滤
|
||
if (selectedColor) {
|
||
filtered = filtered.filter(cat => cat.color === selectedColor)
|
||
}
|
||
|
||
return filtered
|
||
}
|
||
|
||
const filteredCategories = getFilteredCategories()
|
||
|
||
// 刷新分类数据
|
||
const handleRefresh = async () => {
|
||
await refreshCategories()
|
||
}
|
||
|
||
const handleCreateCategory = async () => {
|
||
try {
|
||
const newCategory = await ResourceCategoryServiceV2.createCategory(
|
||
formData.title,
|
||
formData.ai_prompt,
|
||
formData.color,
|
||
formData.is_cloud
|
||
)
|
||
|
||
if (newCategory) {
|
||
// 使用 store 的 addCategory 方法
|
||
addCategory(newCategory)
|
||
setShowCreateForm(false)
|
||
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B', is_cloud: false })
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to create category:', error)
|
||
alert('创建分类失败: ' + (error instanceof Error ? error.message : '未知错误'))
|
||
}
|
||
}
|
||
|
||
const handleUpdateCategory = async () => {
|
||
if (!editingCategory) return
|
||
|
||
try {
|
||
const updatedCategory = await ResourceCategoryServiceV2.updateCategory(
|
||
editingCategory.id,
|
||
{
|
||
title: formData.title,
|
||
ai_prompt: formData.ai_prompt,
|
||
color: formData.color
|
||
}
|
||
)
|
||
|
||
if (updatedCategory) {
|
||
// 使用 store 的 updateCategory 方法
|
||
updateCategory(editingCategory.id, updatedCategory)
|
||
setEditingCategory(null)
|
||
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B', is_cloud: false })
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to update category:', error)
|
||
alert('更新分类失败: ' + (error instanceof Error ? error.message : '未知错误'))
|
||
}
|
||
}
|
||
|
||
const handleDeleteCategory = async (categoryId: string) => {
|
||
if (!confirm('确定要删除这个分类吗?此操作不可恢复。')) return
|
||
|
||
try {
|
||
await ResourceCategoryServiceV2.deleteCategory(categoryId, true)
|
||
// 使用 store 的 removeCategory 方法
|
||
removeCategory(categoryId)
|
||
} catch (error) {
|
||
console.error('Failed to delete category:', error)
|
||
alert('删除分类失败: ' + (error instanceof Error ? error.message : '未知错误'))
|
||
}
|
||
}
|
||
|
||
const handleToggleCategory = async (categoryId: string, isActive: boolean) => {
|
||
try {
|
||
if (isActive) {
|
||
await ResourceCategoryServiceV2.activateCategory(categoryId)
|
||
} else {
|
||
await ResourceCategoryServiceV2.deactivateCategory(categoryId)
|
||
}
|
||
|
||
// 使用 store 的 updateCategory 方法
|
||
updateCategory(categoryId, { is_active: isActive })
|
||
} catch (error) {
|
||
console.error('Failed to toggle category:', error)
|
||
alert('切换分类状态失败: ' + (error instanceof Error ? error.message : '未知错误'))
|
||
}
|
||
}
|
||
|
||
const handleBatchImport = async () => {
|
||
try {
|
||
const batchItems: CategoryBatchItem[] = JSON.parse(batchData)
|
||
const result = await ResourceCategoryServiceV2.batchCreateCategories(batchItems)
|
||
|
||
alert(`批量导入完成: 成功 ${result.success} 个,失败 ${result.failed} 个`)
|
||
setShowBatchImport(false)
|
||
setBatchData('')
|
||
// 刷新分类数据
|
||
await refreshCategories()
|
||
} catch (error) {
|
||
console.error('Failed to batch import:', error)
|
||
alert('批量导入失败: ' + (error instanceof Error ? error.message : '数据格式错误'))
|
||
}
|
||
}
|
||
|
||
const handleSearchByColor = (color: string) => {
|
||
if (color === selectedColor) {
|
||
// 取消颜色筛选
|
||
setSelectedColor('')
|
||
} else {
|
||
// 按颜色筛选
|
||
setSelectedColor(color)
|
||
}
|
||
// 过滤逻辑已经在 getFilteredCategories 中处理
|
||
}
|
||
|
||
const handleSearch = () => {
|
||
// 搜索逻辑已经在 getFilteredCategories 中处理
|
||
// 这里只需要触发重新渲染,React 会自动调用 getFilteredCategories
|
||
}
|
||
|
||
const startEdit = (category: ResourceCategoryV2) => {
|
||
setEditingCategory(category)
|
||
setFormData({
|
||
title: category.title,
|
||
ai_prompt: category.ai_prompt,
|
||
color: category.color,
|
||
is_cloud: category.is_cloud
|
||
})
|
||
setShowCreateForm(false)
|
||
}
|
||
|
||
const cancelEdit = () => {
|
||
setEditingCategory(null)
|
||
setShowCreateForm(false)
|
||
setShowBatchImport(false)
|
||
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B', is_cloud: false })
|
||
setBatchData('')
|
||
}
|
||
|
||
const exportCategories = () => {
|
||
const exportData = categories.map(cat => ({
|
||
title: cat.title,
|
||
ai_prompt: cat.ai_prompt,
|
||
color: cat.color,
|
||
is_cloud: cat.is_cloud
|
||
}))
|
||
|
||
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' })
|
||
const url = URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = 'resource_categories.json'
|
||
a.click()
|
||
URL.revokeObjectURL(url)
|
||
}
|
||
|
||
|
||
|
||
// 统计信息
|
||
const stats = {
|
||
total: categories.length,
|
||
active: categories.filter(c => c.is_active).length,
|
||
cloud: categories.filter(c => c.is_cloud).length,
|
||
user: categories.filter(c => !c.is_cloud).length
|
||
}
|
||
|
||
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">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900">素材分类管理 V2</h1>
|
||
<p className="text-gray-600 mt-2">基于最新API的增强版分类管理,支持云端分类、批量操作等高级功能</p>
|
||
</div>
|
||
|
||
{/* 统计卡片 */}
|
||
<div className="flex space-x-4">
|
||
<div className="bg-blue-50 rounded-lg p-3 text-center min-w-[80px]">
|
||
<div className="text-2xl font-bold text-blue-600">{stats.total}</div>
|
||
<div className="text-xs text-blue-500">总数</div>
|
||
</div>
|
||
<div className="bg-green-50 rounded-lg p-3 text-center min-w-[80px]">
|
||
<div className="text-2xl font-bold text-green-600">{stats.active}</div>
|
||
<div className="text-xs text-green-500">启用</div>
|
||
</div>
|
||
<div className="bg-purple-50 rounded-lg p-3 text-center min-w-[80px]">
|
||
<div className="text-2xl font-bold text-purple-600">{stats.cloud}</div>
|
||
<div className="text-xs text-purple-500">云端</div>
|
||
</div>
|
||
<div className="bg-orange-50 rounded-lg p-3 text-center min-w-[80px]">
|
||
<div className="text-2xl font-bold text-orange-600">{stats.user}</div>
|
||
<div className="text-xs text-orange-500">用户</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 搜索和操作栏 */}
|
||
<div className="flex items-center justify-between mb-6">
|
||
<div className="flex items-center space-x-4 flex-1">
|
||
{/* 搜索框 */}
|
||
<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)}
|
||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||
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>
|
||
|
||
{/* 筛选选项 */}
|
||
<div className="flex items-center space-x-3">
|
||
<label className="flex items-center text-sm text-gray-600">
|
||
<input
|
||
type="checkbox"
|
||
checked={includeCloud}
|
||
onChange={(e) => setIncludeCloud(e.target.checked)}
|
||
className="mr-2"
|
||
/>
|
||
<Cloud size={16} className="mr-1" />
|
||
包含云端
|
||
</label>
|
||
|
||
<label className="flex items-center text-sm text-gray-600">
|
||
<input
|
||
type="checkbox"
|
||
checked={showDisabled}
|
||
onChange={(e) => setShowDisabled(e.target.checked)}
|
||
className="mr-2"
|
||
/>
|
||
显示禁用
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 操作按钮 */}
|
||
<div className="flex items-center space-x-3">
|
||
<button
|
||
onClick={exportCategories}
|
||
className="flex items-center px-3 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||
title="导出分类"
|
||
>
|
||
<Download size={16} className="mr-2" />
|
||
导出
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setShowBatchImport(true)}
|
||
className="flex items-center px-3 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||
title="批量导入"
|
||
>
|
||
<Upload size={16} className="mr-2" />
|
||
批量导入
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => {
|
||
setShowCreateForm(true)
|
||
setEditingCategory(null)
|
||
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B', is_cloud: false })
|
||
}}
|
||
className="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>
|
||
|
||
{/* 颜色筛选栏 */}
|
||
<div className="mb-6">
|
||
<div className="flex items-center space-x-2">
|
||
<Palette size={16} className="text-gray-500" />
|
||
<span className="text-sm text-gray-600">按颜色筛选:</span>
|
||
<div className="flex space-x-2">
|
||
{presetColors.slice(0, 12).map((color) => (
|
||
<button
|
||
key={color}
|
||
onClick={() => handleSearchByColor(color)}
|
||
className={`w-6 h-6 rounded-full border-2 transition-all ${
|
||
selectedColor === color
|
||
? 'border-gray-800 scale-110'
|
||
: 'border-gray-300 hover:border-gray-400'
|
||
}`}
|
||
style={{ backgroundColor: color }}
|
||
title={`筛选颜色: ${color}`}
|
||
/>
|
||
))}
|
||
{selectedColor && (
|
||
<button
|
||
onClick={() => handleSearchByColor('')}
|
||
className="px-2 py-1 text-xs bg-gray-100 text-gray-600 rounded hover:bg-gray-200"
|
||
>
|
||
清除
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</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 p-6 transition-all ${
|
||
category.is_active
|
||
? 'border-gray-200'
|
||
: 'border-gray-300 bg-gray-50 opacity-75'
|
||
}`}>
|
||
{/* 分类标题和标识 */}
|
||
<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 ${category.is_active ? 'text-gray-900' : 'text-gray-400'}`}>
|
||
{category.title}
|
||
</h3>
|
||
|
||
{/* 状态标识 */}
|
||
<div className="flex items-center ml-2 space-x-1">
|
||
{category.is_cloud && (
|
||
<span className="px-2 py-1 text-xs bg-purple-100 text-purple-600 rounded flex items-center">
|
||
<Cloud size={12} className="mr-1" />
|
||
云端
|
||
</span>
|
||
)}
|
||
{!category.is_active && (
|
||
<span className="px-2 py-1 text-xs bg-gray-100 text-gray-500 rounded">
|
||
已禁用
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2">
|
||
{/* 启用/禁用开关 */}
|
||
<label className="relative inline-flex items-center cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={category.is_active}
|
||
onChange={(e) => handleToggleCategory(category.id, e.target.checked)}
|
||
className="sr-only peer"
|
||
/>
|
||
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
|
||
</label>
|
||
|
||
<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="删除"
|
||
disabled={category.is_cloud}
|
||
>
|
||
<Trash2 size={16} />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* AI提示词 */}
|
||
<div className="mb-4">
|
||
<p className="text-sm text-gray-600 leading-relaxed line-clamp-3">{category.ai_prompt}</p>
|
||
</div>
|
||
|
||
{/* 分类信息 */}
|
||
<div className="flex items-center justify-between text-xs text-gray-400">
|
||
<div className="flex items-center space-x-2">
|
||
<Users size={12} />
|
||
<span>{category.user_id}</span>
|
||
</div>
|
||
<div>
|
||
创建于 {new Date(category.created_at).toLocaleDateString()}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 空状态 */}
|
||
{filteredCategories.length === 0 && (
|
||
<div className="text-center py-12">
|
||
<div className="text-gray-400 mb-4">
|
||
{searchTerm || selectedColor ? '没有找到匹配的分类' : '暂无分类'}
|
||
</div>
|
||
{!searchTerm && !selectedColor && (
|
||
<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-8 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>
|
||
|
||
{/* 云端分类选项 */}
|
||
{!editingCategory && (
|
||
<div>
|
||
<label className="flex items-center text-sm text-gray-700">
|
||
<input
|
||
type="checkbox"
|
||
checked={formData.is_cloud}
|
||
onChange={(e) => setFormData({ ...formData, is_cloud: e.target.checked })}
|
||
className="mr-2"
|
||
/>
|
||
<Cloud size={16} className="mr-1" />
|
||
设为云端公共分类
|
||
</label>
|
||
<p className="text-xs text-gray-500 mt-1">云端分类对所有用户可见</p>
|
||
</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()}
|
||
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>
|
||
)}
|
||
|
||
{/* 批量导入弹窗 */}
|
||
{showBatchImport && (
|
||
<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-2xl 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">批量导入分类</h2>
|
||
<button
|
||
onClick={cancelEdit}
|
||
className="text-gray-400 hover:text-gray-600"
|
||
>
|
||
<X size={24} />
|
||
</button>
|
||
</div>
|
||
|
||
{/* 导入说明 */}
|
||
<div className="p-6">
|
||
<div className="mb-4">
|
||
<h3 className="text-sm font-medium text-gray-700 mb-2">数据格式说明</h3>
|
||
<div className="bg-gray-50 rounded-lg p-4 text-sm">
|
||
<p className="mb-2">请输入JSON格式的分类数据,格式如下:</p>
|
||
<pre className="text-xs text-gray-600 bg-white p-2 rounded border overflow-x-auto">
|
||
{`[
|
||
{
|
||
"title": "视频素材",
|
||
"ai_prompt": "用于识别视频文件",
|
||
"color": "#FF6B6B",
|
||
"is_cloud": false
|
||
},
|
||
{
|
||
"title": "音频素材",
|
||
"ai_prompt": "用于识别音频文件",
|
||
"color": "#4ECDC4"
|
||
}
|
||
]`}
|
||
</pre>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 数据输入 */}
|
||
<div className="mb-4">
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
分类数据 (JSON格式)
|
||
</label>
|
||
<textarea
|
||
value={batchData}
|
||
onChange={(e) => setBatchData(e.target.value)}
|
||
placeholder="请输入JSON格式的分类数据..."
|
||
rows={10}
|
||
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 font-mono text-sm"
|
||
/>
|
||
</div>
|
||
|
||
{/* 操作按钮 */}
|
||
<div className="flex items-center justify-end space-x-3">
|
||
<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={handleBatchImport}
|
||
disabled={!batchData.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"
|
||
>
|
||
<Upload size={16} className="mr-2" />
|
||
导入
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default ResourceCategoryPageV2
|