671 lines
22 KiB
TypeScript
671 lines
22 KiB
TypeScript
/**
|
|
* 分类管理组件
|
|
* 提取自 app/admin/page.tsx 的分类管理功能
|
|
* 包含:店铺分类、场景分类、平台分类、品类管理
|
|
*/
|
|
|
|
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import type { Category, Scene, Platform, ProductCategory } from '@/hooks/useAdminState'
|
|
|
|
interface CategoryManagementProps {
|
|
categories: Category[]
|
|
setCategories: React.Dispatch<React.SetStateAction<Category[]>>
|
|
scenes: Scene[]
|
|
setScenes: React.Dispatch<React.SetStateAction<Scene[]>>
|
|
platforms: Platform[]
|
|
setPlatforms: React.Dispatch<React.SetStateAction<Platform[]>>
|
|
productCategories: ProductCategory[]
|
|
setProductCategories: React.Dispatch<React.SetStateAction<ProductCategory[]>>
|
|
showToast: (message: string, type?: 'success' | 'error') => void
|
|
}
|
|
|
|
export default function CategoryManagement({
|
|
categories,
|
|
setCategories,
|
|
scenes,
|
|
setScenes,
|
|
platforms,
|
|
setPlatforms,
|
|
productCategories,
|
|
setProductCategories,
|
|
showToast,
|
|
}: CategoryManagementProps) {
|
|
// 编辑状态
|
|
const [editingCategory, setEditingCategory] = useState<Category | null>(null)
|
|
const [editingScene, setEditingScene] = useState<Scene | null>(null)
|
|
const [editingPlatform, setEditingPlatform] = useState<Platform | null>(null)
|
|
const [editingProductCategory, setEditingProductCategory] = useState<ProductCategory | null>(null)
|
|
|
|
// 添加弹窗状态
|
|
const [showAddCategoryModal, setShowAddCategoryModal] = useState(false)
|
|
const [showAddSceneModal, setShowAddSceneModal] = useState(false)
|
|
const [showAddPlatformModal, setShowAddPlatformModal] = useState(false)
|
|
const [showAddProductCategoryModal, setShowAddProductCategoryModal] = useState(false)
|
|
|
|
// 新建输入状态
|
|
const [newCategoryName, setNewCategoryName] = useState('')
|
|
const [newSceneName, setNewSceneName] = useState('')
|
|
const [newPlatformName, setNewPlatformName] = useState('')
|
|
const [newProductCategoryName, setNewProductCategoryName] = useState('')
|
|
|
|
// ========== 店铺分类操作 ==========
|
|
const handleAddCategory = async () => {
|
|
if (!newCategoryName.trim()) {
|
|
showToast('分类名称不能为空', 'error')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/categories', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name: newCategoryName.trim() }),
|
|
})
|
|
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
setCategories(prev => [...prev, { id: data.id, name: newCategoryName.trim(), count: 0 }])
|
|
setShowAddCategoryModal(false)
|
|
setNewCategoryName('')
|
|
showToast('添加成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '添加失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error adding category:', error)
|
|
showToast('添加失败', 'error')
|
|
}
|
|
}
|
|
|
|
const handleUpdateCategory = async (id: string, newName: string) => {
|
|
if (!newName.trim()) {
|
|
showToast('分类名称不能为空', 'error')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/categories', {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id, name: newName.trim() }),
|
|
})
|
|
|
|
if (response.ok) {
|
|
setCategories(prev =>
|
|
prev.map(cat => cat.id === id ? { ...cat, name: newName.trim() } : cat)
|
|
)
|
|
setEditingCategory(null)
|
|
showToast('修改成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '修改失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating category:', error)
|
|
showToast('修改失败', 'error')
|
|
}
|
|
}
|
|
|
|
const handleDeleteCategory = async (id: string) => {
|
|
if (!confirm('确定要删除这个使用店铺分类吗?')) return
|
|
|
|
try {
|
|
const response = await fetch(`/api/categories?id=${id}`, { method: 'DELETE' })
|
|
|
|
if (response.ok) {
|
|
setCategories(prev => prev.filter(cat => cat.id !== id))
|
|
showToast('删除成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '删除失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting category:', error)
|
|
showToast('删除失败', 'error')
|
|
}
|
|
}
|
|
|
|
// ========== 场景分类操作 ==========
|
|
const handleAddScene = async () => {
|
|
if (!newSceneName.trim()) {
|
|
showToast('场景名称不能为空', 'error')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/scenes', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name: newSceneName.trim() }),
|
|
})
|
|
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
setScenes(prev => [...prev, { id: data.id, name: newSceneName.trim(), count: 0 }])
|
|
setShowAddSceneModal(false)
|
|
setNewSceneName('')
|
|
showToast('添加成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '添加失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error adding scene:', error)
|
|
showToast('添加失败', 'error')
|
|
}
|
|
}
|
|
|
|
const handleUpdateScene = async (id: string, newName: string) => {
|
|
if (!newName.trim()) {
|
|
showToast('场景名称不能为空', 'error')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/scenes', {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id, name: newName.trim() }),
|
|
})
|
|
|
|
if (response.ok) {
|
|
setScenes(prev =>
|
|
prev.map(scene => scene.id === id ? { ...scene, name: newName.trim() } : scene)
|
|
)
|
|
setEditingScene(null)
|
|
showToast('修改成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '修改失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating scene:', error)
|
|
showToast('修改失败', 'error')
|
|
}
|
|
}
|
|
|
|
const handleDeleteScene = async (id: string) => {
|
|
if (!confirm('确定要删除这个场景类别吗?')) return
|
|
|
|
try {
|
|
const response = await fetch(`/api/scenes?id=${id}`, { method: 'DELETE' })
|
|
|
|
if (response.ok) {
|
|
setScenes(prev => prev.filter(scene => scene.id !== id))
|
|
showToast('删除成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '删除失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting scene:', error)
|
|
showToast('删除失败', 'error')
|
|
}
|
|
}
|
|
|
|
// ========== 平台分类操作 ==========
|
|
const handleAddPlatform = async () => {
|
|
if (!newPlatformName.trim()) {
|
|
showToast('平台名称不能为空', 'error')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/platforms', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name: newPlatformName.trim() }),
|
|
})
|
|
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
setPlatforms(prev => [...prev, { id: data.id, name: newPlatformName.trim(), count: 0 }])
|
|
setShowAddPlatformModal(false)
|
|
setNewPlatformName('')
|
|
showToast('添加成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '添加失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error adding platform:', error)
|
|
showToast('添加失败', 'error')
|
|
}
|
|
}
|
|
|
|
const handleUpdatePlatform = async (id: string, newName: string) => {
|
|
if (!newName.trim()) {
|
|
showToast('平台名称不能为空', 'error')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/platforms', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id, name: newName.trim() }),
|
|
})
|
|
|
|
if (response.ok) {
|
|
setPlatforms(prev =>
|
|
prev.map(platform => platform.id === id ? { ...platform, name: newName.trim() } : platform)
|
|
)
|
|
setEditingPlatform(null)
|
|
showToast('修改成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '修改失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating platform:', error)
|
|
showToast('修改失败', 'error')
|
|
}
|
|
}
|
|
|
|
const handleDeletePlatform = async (id: string) => {
|
|
if (!confirm('确定要删除这个平台分类吗?')) return
|
|
|
|
try {
|
|
const response = await fetch(`/api/platforms?id=${id}`, { method: 'DELETE' })
|
|
|
|
if (response.ok) {
|
|
setPlatforms(prev => prev.filter(platform => platform.id !== id))
|
|
showToast('删除成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '删除失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting platform:', error)
|
|
showToast('删除失败', 'error')
|
|
}
|
|
}
|
|
|
|
// ========== 品类操作 ==========
|
|
const handleAddProductCategory = async () => {
|
|
if (!newProductCategoryName.trim()) {
|
|
showToast('请输入品类名称', 'error')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/product-categories', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name: newProductCategoryName.trim() }),
|
|
})
|
|
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
setProductCategories(prev => [...prev, { id: data.id, name: newProductCategoryName.trim(), count: 0 }])
|
|
setShowAddProductCategoryModal(false)
|
|
setNewProductCategoryName('')
|
|
showToast('添加成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '添加失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error adding product category:', error)
|
|
showToast('添加失败', 'error')
|
|
}
|
|
}
|
|
|
|
const handleUpdateProductCategory = async (id: string, newName: string) => {
|
|
if (!newName.trim()) {
|
|
showToast('品类名称不能为空', 'error')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/product-categories', {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id, name: newName.trim() }),
|
|
})
|
|
|
|
if (response.ok) {
|
|
setProductCategories(prev =>
|
|
prev.map(cat => cat.id === id ? { ...cat, name: newName.trim() } : cat)
|
|
)
|
|
setEditingProductCategory(null)
|
|
showToast('修改成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '修改失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating product category:', error)
|
|
showToast('修改失败', 'error')
|
|
}
|
|
}
|
|
|
|
const handleDeleteProductCategory = async (id: string) => {
|
|
if (!confirm('确定要删除这个品类吗?')) return
|
|
|
|
try {
|
|
const response = await fetch(`/api/product-categories?id=${id}`, { method: 'DELETE' })
|
|
|
|
if (response.ok) {
|
|
setProductCategories(prev => prev.filter(cat => cat.id !== id))
|
|
showToast('删除成功')
|
|
} else {
|
|
const data = await response.json()
|
|
showToast(data.error || '删除失败', 'error')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting product category:', error)
|
|
showToast('删除失败', 'error')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{/* 使用店铺分类 */}
|
|
<CategorySection
|
|
title="使用店铺分类"
|
|
description="管理所有使用店铺分类名称"
|
|
addButtonText="新增店铺"
|
|
items={categories}
|
|
editingItem={editingCategory}
|
|
onAdd={() => setShowAddCategoryModal(true)}
|
|
onEdit={setEditingCategory}
|
|
onUpdate={handleUpdateCategory}
|
|
onDelete={handleDeleteCategory}
|
|
onCancelEdit={() => setEditingCategory(null)}
|
|
/>
|
|
|
|
{/* 场景类别 */}
|
|
<CategorySection
|
|
title="场景类别"
|
|
description="管理所有场景类别名称"
|
|
addButtonText="新增场景"
|
|
items={scenes}
|
|
editingItem={editingScene}
|
|
onAdd={() => setShowAddSceneModal(true)}
|
|
onEdit={setEditingScene}
|
|
onUpdate={handleUpdateScene}
|
|
onDelete={handleDeleteScene}
|
|
onCancelEdit={() => setEditingScene(null)}
|
|
/>
|
|
|
|
{/* 平台分类 */}
|
|
<CategorySection
|
|
title="平台分类"
|
|
description="管理所有平台分类名称"
|
|
addButtonText="新增平台"
|
|
items={platforms}
|
|
editingItem={editingPlatform}
|
|
onAdd={() => setShowAddPlatformModal(true)}
|
|
onEdit={setEditingPlatform}
|
|
onUpdate={handleUpdatePlatform}
|
|
onDelete={handleDeletePlatform}
|
|
onCancelEdit={() => setEditingPlatform(null)}
|
|
/>
|
|
|
|
{/* 品类管理 */}
|
|
<CategorySection
|
|
title="品类管理"
|
|
description="管理所有品类名称"
|
|
addButtonText="新增品类"
|
|
items={productCategories}
|
|
editingItem={editingProductCategory}
|
|
onAdd={() => setShowAddProductCategoryModal(true)}
|
|
onEdit={setEditingProductCategory}
|
|
onUpdate={handleUpdateProductCategory}
|
|
onDelete={handleDeleteProductCategory}
|
|
onCancelEdit={() => setEditingProductCategory(null)}
|
|
/>
|
|
|
|
{/* 添加弹窗 */}
|
|
<AddModal
|
|
show={showAddCategoryModal}
|
|
title="新增店铺分类"
|
|
placeholder="请输入店铺名称"
|
|
value={newCategoryName}
|
|
onChange={setNewCategoryName}
|
|
onConfirm={handleAddCategory}
|
|
onCancel={() => {
|
|
setShowAddCategoryModal(false)
|
|
setNewCategoryName('')
|
|
}}
|
|
/>
|
|
|
|
<AddModal
|
|
show={showAddSceneModal}
|
|
title="新增场景类别"
|
|
placeholder="请输入场景名称"
|
|
value={newSceneName}
|
|
onChange={setNewSceneName}
|
|
onConfirm={handleAddScene}
|
|
onCancel={() => {
|
|
setShowAddSceneModal(false)
|
|
setNewSceneName('')
|
|
}}
|
|
/>
|
|
|
|
<AddModal
|
|
show={showAddPlatformModal}
|
|
title="新增平台分类"
|
|
placeholder="请输入平台名称"
|
|
value={newPlatformName}
|
|
onChange={setNewPlatformName}
|
|
onConfirm={handleAddPlatform}
|
|
onCancel={() => {
|
|
setShowAddPlatformModal(false)
|
|
setNewPlatformName('')
|
|
}}
|
|
/>
|
|
|
|
<AddModal
|
|
show={showAddProductCategoryModal}
|
|
title="新增品类"
|
|
placeholder="请输入品类名称"
|
|
value={newProductCategoryName}
|
|
onChange={setNewProductCategoryName}
|
|
onConfirm={handleAddProductCategory}
|
|
onCancel={() => {
|
|
setShowAddProductCategoryModal(false)
|
|
setNewProductCategoryName('')
|
|
}}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 分类区块组件
|
|
interface CategoryItem {
|
|
id: string
|
|
name: string
|
|
count: number
|
|
}
|
|
|
|
interface CategorySectionProps {
|
|
title: string
|
|
description: string
|
|
addButtonText: string
|
|
items: CategoryItem[]
|
|
editingItem: CategoryItem | null
|
|
onAdd: () => void
|
|
onEdit: (item: CategoryItem) => void
|
|
onUpdate: (id: string, newName: string) => void
|
|
onDelete: (id: string) => void
|
|
onCancelEdit: () => void
|
|
}
|
|
|
|
function CategorySection({
|
|
title,
|
|
description,
|
|
addButtonText,
|
|
items,
|
|
editingItem,
|
|
onAdd,
|
|
onEdit,
|
|
onUpdate,
|
|
onDelete,
|
|
onCancelEdit,
|
|
}: CategorySectionProps) {
|
|
const [editValue, setEditValue] = useState('')
|
|
|
|
const handleStartEdit = (item: CategoryItem) => {
|
|
onEdit(item)
|
|
setEditValue(item.name)
|
|
}
|
|
|
|
const handleConfirmEdit = (id: string) => {
|
|
onUpdate(id, editValue)
|
|
setEditValue('')
|
|
}
|
|
|
|
const handleCancelEdit = () => {
|
|
onCancelEdit()
|
|
setEditValue('')
|
|
}
|
|
|
|
return (
|
|
<div className="card-apple">
|
|
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">{title}</h2>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">{description}</p>
|
|
</div>
|
|
<button
|
|
onClick={onAdd}
|
|
className="px-4 py-2 bg-gray-900 dark:bg-gray-700 text-white text-sm rounded-lg hover:bg-gray-800 dark:hover:bg-gray-600 transition-colors flex items-center gap-2"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
|
</svg>
|
|
{addButtonText}
|
|
</button>
|
|
</div>
|
|
<div className="p-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{items.map((item) => (
|
|
<div
|
|
key={item.id}
|
|
className="border border-gray-200 dark:border-gray-600 rounded-lg p-4 hover:shadow-md transition-shadow"
|
|
>
|
|
{editingItem?.id === item.id ? (
|
|
<div className="space-y-3">
|
|
<input
|
|
type="text"
|
|
value={editValue}
|
|
onChange={(e) => setEditValue(e.target.value)}
|
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-gray-400 outline-none dark:bg-gray-700 dark:text-white"
|
|
autoFocus
|
|
/>
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={() => handleConfirmEdit(item.id)}
|
|
className="flex-1 px-3 py-1.5 bg-gray-900 dark:bg-gray-600 text-white text-sm rounded-lg hover:bg-gray-800 dark:hover:bg-gray-500 transition-colors"
|
|
>
|
|
保存
|
|
</button>
|
|
<button
|
|
onClick={handleCancelEdit}
|
|
className="flex-1 px-3 py-1.5 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 text-sm rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
|
>
|
|
取消
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h3 className="font-medium text-gray-900 dark:text-white">{item.name}</h3>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">{item.count} 个素材</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={() => handleStartEdit(item)}
|
|
className="p-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
</svg>
|
|
</button>
|
|
<button
|
|
onClick={() => onDelete(item.id)}
|
|
className="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
{items.length === 0 && (
|
|
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
暂无数据
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 添加弹窗组件
|
|
interface AddModalProps {
|
|
show: boolean
|
|
title: string
|
|
placeholder: string
|
|
value: string
|
|
onChange: (value: string) => void
|
|
onConfirm: () => void
|
|
onCancel: () => void
|
|
}
|
|
|
|
function AddModal({
|
|
show,
|
|
title,
|
|
placeholder,
|
|
value,
|
|
onChange,
|
|
onConfirm,
|
|
onCancel,
|
|
}: AddModalProps) {
|
|
if (!show) return null
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-96 max-w-[90vw]">
|
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">{title}</h3>
|
|
<input
|
|
type="text"
|
|
placeholder={placeholder}
|
|
value={value}
|
|
onChange={(e) => onChange(e.target.value)}
|
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-gray-400 outline-none dark:bg-gray-700 dark:text-white mb-4"
|
|
autoFocus
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter') onConfirm()
|
|
if (e.key === 'Escape') onCancel()
|
|
}}
|
|
/>
|
|
<div className="flex justify-end gap-3">
|
|
<button
|
|
onClick={onCancel}
|
|
className="px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
|
>
|
|
取消
|
|
</button>
|
|
<button
|
|
onClick={onConfirm}
|
|
className="px-4 py-2 bg-gray-900 dark:bg-gray-600 text-white rounded-lg hover:bg-gray-800 dark:hover:bg-gray-500 transition-colors"
|
|
>
|
|
确定
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|