2025-12-05 20:07:50 +08:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { motion } from 'framer-motion';
|
|
|
|
|
import { UserIcon, PencilIcon, TrashIcon, CheckIcon } from '@heroicons/react/24/outline';
|
|
|
|
|
import SkinViewer from '@/components/SkinViewer';
|
|
|
|
|
import type { Profile } from '@/lib/api';
|
|
|
|
|
|
|
|
|
|
interface CharacterCardProps {
|
|
|
|
|
profile: Profile;
|
|
|
|
|
skinUrl?: string;
|
|
|
|
|
isSlim?: boolean;
|
|
|
|
|
isEditing?: boolean;
|
|
|
|
|
editName?: string;
|
|
|
|
|
onEdit: (uuid: string, currentName: string) => void;
|
|
|
|
|
onSave: (uuid: string) => void;
|
|
|
|
|
onCancel: () => void;
|
|
|
|
|
onDelete: (uuid: string) => void;
|
|
|
|
|
onSetActive: (uuid: string) => void;
|
|
|
|
|
onSelectSkin: (uuid: string) => void;
|
|
|
|
|
onEditNameChange: (name: string) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function CharacterCard({
|
|
|
|
|
profile,
|
|
|
|
|
skinUrl,
|
|
|
|
|
isSlim,
|
|
|
|
|
isEditing,
|
|
|
|
|
editName,
|
|
|
|
|
onEdit,
|
|
|
|
|
onSave,
|
|
|
|
|
onCancel,
|
|
|
|
|
onDelete,
|
|
|
|
|
onSetActive,
|
|
|
|
|
onSelectSkin,
|
|
|
|
|
onEditNameChange
|
|
|
|
|
}: CharacterCardProps) {
|
|
|
|
|
return (
|
|
|
|
|
<motion.div
|
|
|
|
|
key={profile.uuid}
|
|
|
|
|
className="bg-white/50 dark:bg-gray-800/50 backdrop-blur-lg rounded-2xl p-6 border border-white/20 dark:border-gray-700/50 shadow-lg"
|
|
|
|
|
whileHover={{ scale: 1.02, y: -5 }}
|
|
|
|
|
transition={{ duration: 0.2 }}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
2026-02-24 11:09:37 +08:00
|
|
|
<div className="flex items-center gap-2 flex-1">
|
|
|
|
|
{isEditing ? (
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={editName}
|
|
|
|
|
onChange={(e) => onEditNameChange(e.target.value)}
|
|
|
|
|
className="text-lg font-semibold bg-transparent border-b border-orange-500 focus:outline-none text-gray-900 dark:text-white flex-1"
|
|
|
|
|
onBlur={() => onSave(profile.uuid)}
|
|
|
|
|
onKeyPress={(e) => e.key === 'Enter' && onSave(profile.uuid)}
|
|
|
|
|
autoFocus
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white truncate">{profile.name}</h3>
|
|
|
|
|
<motion.button
|
|
|
|
|
onClick={() => onEdit(profile.uuid, profile.name)}
|
|
|
|
|
className="text-gray-500 hover:text-orange-500 transition-colors"
|
|
|
|
|
whileHover={{ scale: 1.1 }}
|
|
|
|
|
whileTap={{ scale: 0.9 }}
|
|
|
|
|
title="改名"
|
|
|
|
|
>
|
|
|
|
|
<PencilIcon className="w-4 h-4" />
|
|
|
|
|
</motion.button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2025-12-05 20:07:50 +08:00
|
|
|
{profile.is_active && (
|
|
|
|
|
<span className="px-2 py-1 bg-gradient-to-r from-green-500 to-emerald-500 text-white text-xs rounded-full flex items-center space-x-1">
|
|
|
|
|
<CheckIcon className="w-3 h-3" />
|
|
|
|
|
<span>当前使用</span>
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="aspect-square bg-gradient-to-br from-orange-100 to-amber-100 dark:from-gray-700 dark:to-gray-600 rounded-xl mb-4 flex items-center justify-center relative overflow-hidden">
|
|
|
|
|
{skinUrl ? (
|
|
|
|
|
<SkinViewer
|
|
|
|
|
skinUrl={skinUrl}
|
|
|
|
|
isSlim={isSlim}
|
2026-02-24 11:09:37 +08:00
|
|
|
width={180}
|
|
|
|
|
height={180}
|
2025-12-05 20:07:50 +08:00
|
|
|
autoRotate={false}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<motion.div
|
|
|
|
|
className="w-20 h-20 bg-gradient-to-br from-orange-400 to-amber-500 rounded-lg shadow-lg flex items-center justify-center"
|
|
|
|
|
whileHover={{ scale: 1.1, rotate: 5 }}
|
|
|
|
|
transition={{ type: 'spring', stiffness: 300 }}
|
|
|
|
|
>
|
|
|
|
|
<UserIcon className="w-10 h-10 text-white" />
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 操作按钮 */}
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
{isEditing ? (
|
|
|
|
|
<>
|
|
|
|
|
<motion.button
|
|
|
|
|
onClick={() => onSave(profile.uuid)}
|
|
|
|
|
className="flex-1 bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white text-sm py-2 px-3 rounded-lg transition-all duration-200"
|
|
|
|
|
whileHover={{ scale: 1.02 }}
|
|
|
|
|
whileTap={{ scale: 0.98 }}
|
|
|
|
|
>
|
|
|
|
|
保存
|
|
|
|
|
</motion.button>
|
|
|
|
|
<motion.button
|
|
|
|
|
onClick={onCancel}
|
|
|
|
|
className="flex-1 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 text-sm py-2 px-3 rounded-lg transition-all duration-200"
|
|
|
|
|
whileHover={{ scale: 1.02 }}
|
|
|
|
|
whileTap={{ scale: 0.98 }}
|
|
|
|
|
>
|
|
|
|
|
取消
|
|
|
|
|
</motion.button>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<motion.button
|
2026-02-24 11:09:37 +08:00
|
|
|
onClick={() => onSelectSkin(profile.uuid)}
|
|
|
|
|
className="flex-1 bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-600 hover:to-amber-600 text-white text-sm py-2 px-3 rounded-lg transition-all duration-200"
|
2025-12-05 20:07:50 +08:00
|
|
|
whileHover={{ scale: 1.02 }}
|
|
|
|
|
whileTap={{ scale: 0.98 }}
|
|
|
|
|
>
|
2026-02-24 11:09:37 +08:00
|
|
|
修改皮肤
|
2025-12-05 20:07:50 +08:00
|
|
|
</motion.button>
|
|
|
|
|
<motion.button
|
|
|
|
|
onClick={() => onDelete(profile.uuid)}
|
|
|
|
|
className="px-3 py-2 border border-red-500 text-red-500 hover:bg-red-500 hover:text-white rounded-lg transition-all duration-200"
|
|
|
|
|
whileHover={{ scale: 1.02 }}
|
|
|
|
|
whileTap={{ scale: 0.98 }}
|
|
|
|
|
>
|
|
|
|
|
<TrashIcon className="w-4 h-4" />
|
|
|
|
|
</motion.button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
);
|
|
|
|
|
}
|