MaterialCard.jsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import React, { useState } from 'react';
  2. import { Card, CardContent } from '@/components/ui/card';
  3. import { Button } from '@/components/ui/button';
  4. import { Input } from '@/components/ui/input';
  5. import { Badge } from '@/components/ui/badge';
  6. import { Trash2, Edit, X } from 'lucide-react';
  7. // 获取图片URL的函数
  8. const getImageUrl = (item) => {
  9. if (!item.stored_path) return '';
  10. // 从完整路径中提取文件名
  11. const filename = item.stored_path.split(/[\\/]/).pop();
  12. if (!filename) return '';
  13. // 构建完整的图片URL
  14. const baseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
  15. return `${baseURL}/materials/${filename}`;
  16. };
  17. // 获取素材类型标签配置
  18. const getMaterialTypeConfig = (imageType) => {
  19. switch (imageType) {
  20. case 'face':
  21. return {
  22. label: 'IP',
  23. className: 'bg-red-500 text-white border-red-500'
  24. };
  25. case 'cloth':
  26. return {
  27. label: '服装',
  28. className: 'bg-blue-500 text-white border-blue-500'
  29. };
  30. case 'original':
  31. return {
  32. label: '原图',
  33. className: 'bg-purple-500 text-white border-purple-500'
  34. };
  35. default:
  36. return null;
  37. }
  38. };
  39. export default function MaterialCard({ material, onDelete, onUpdateName }) {
  40. const [isEditing, setIsEditing] = useState(false);
  41. const [editName, setEditName] = useState(material.name || material.original_filename || '');
  42. const handleSaveName = async () => {
  43. if (editName.trim() !== material.name) {
  44. await onUpdateName(material.id, editName.trim());
  45. }
  46. setIsEditing(false);
  47. };
  48. const handleCancelEdit = () => {
  49. setEditName(material.name || material.original_filename || '');
  50. setIsEditing(false);
  51. };
  52. const typeConfig = getMaterialTypeConfig(material.image_type);
  53. return (
  54. <Card className="overflow-hidden">
  55. <div className="aspect-square bg-gray-100 relative group">
  56. <img
  57. src={getImageUrl(material)}
  58. alt={material.name || material.original_filename}
  59. className="w-full h-full object-cover"
  60. onError={(e) => {
  61. console.error('图片加载失败:', getImageUrl(material));
  62. e.target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik01MCAxMDBMMTAwIDUwTDE1MCAxMDBMMTAwIDE1MFYxMDBIMFYxMDBaIiBmaWxsPSIjOUI5QkEwIi8+Cjwvc3ZnPgo=';
  63. }}
  64. />
  65. {/* 删除按钮 */}
  66. <div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
  67. <Button
  68. variant="destructive"
  69. size="sm"
  70. onClick={(e) => {
  71. e.stopPropagation();
  72. onDelete(material.id);
  73. }}
  74. >
  75. <Trash2 className="w-4 h-4" />
  76. </Button>
  77. </div>
  78. {/* 素材类型标签 */}
  79. {typeConfig && (
  80. <div className="absolute top-2 right-2">
  81. <Badge
  82. className={`text-xs font-medium px-2 py-1 ${typeConfig.className}`}
  83. >
  84. {typeConfig.label}
  85. </Badge>
  86. </div>
  87. )}
  88. </div>
  89. <CardContent className="p-3">
  90. {isEditing ? (
  91. <div className="flex gap-2">
  92. <Input
  93. value={editName}
  94. onChange={(e) => setEditName(e.target.value)}
  95. className="flex-1"
  96. autoFocus
  97. onKeyDown={(e) => {
  98. if (e.key === 'Enter') handleSaveName();
  99. if (e.key === 'Escape') handleCancelEdit();
  100. }}
  101. />
  102. <Button size="sm" onClick={handleSaveName}>
  103. 保存
  104. </Button>
  105. <Button size="sm" variant="outline" onClick={handleCancelEdit}>
  106. <X className="w-4 h-4" />
  107. </Button>
  108. </div>
  109. ) : (
  110. <div className="flex items-center justify-between">
  111. <span className="text-sm font-medium truncate flex-1">
  112. {material.name || material.original_filename}
  113. </span>
  114. <Button
  115. size="sm"
  116. variant="ghost"
  117. onClick={() => setIsEditing(true)}
  118. className="ml-2"
  119. >
  120. <Edit className="w-4 h-4" />
  121. </Button>
  122. </div>
  123. )}
  124. </CardContent>
  125. </Card>
  126. );
  127. }