完善动画

This commit is contained in:
小朱
2025-07-10 18:11:10 +08:00
parent 1b13783f48
commit 3c8309163c
5 changed files with 144 additions and 48 deletions

View File

@@ -19,29 +19,34 @@ export const ConfirmDeleteDialog: React.FC<ConfirmDeleteDialogProps> = ({
const [deleteDirectory, setDeleteDirectory] = React.useState(false)
const [isVisible, setIsVisible] = useState(false)
const [isAnimating, setIsAnimating] = useState(false)
const [isClosing, setIsClosing] = useState(false)
useEffect(() => {
if (isOpen) {
setIsClosing(false)
setIsVisible(true)
setTimeout(() => setIsAnimating(true), 10)
} else {
setIsAnimating(false)
setTimeout(() => setIsVisible(false), 200)
setIsClosing(true)
setTimeout(() => setIsVisible(false), 300)
}
}, [isOpen])
const handleCancel = () => {
setIsAnimating(false)
setIsClosing(true)
setTimeout(() => {
onCancel()
}, 200)
}, 300)
}
const handleConfirm = () => {
setIsAnimating(false)
setIsClosing(true)
setTimeout(() => {
onConfirm(deleteDirectory)
}, 200)
}, 300)
}
if (!isVisible) return null
@@ -52,15 +57,15 @@ export const ConfirmDeleteDialog: React.FC<ConfirmDeleteDialogProps> = ({
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* 背景遮罩 */}
<div
className={`absolute inset-0 bg-black transition-opacity duration-200 ${
isAnimating ? 'bg-opacity-50' : 'bg-opacity-0'
className={`absolute inset-0 bg-black/50 ${
isClosing ? 'animate-fade-out' : isAnimating ? 'animate-fade-in' : 'opacity-0'
}`}
onClick={handleCancel}
/>
{/* 对话框内容 */}
<div className={`relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full mx-4 p-6 transform transition-all duration-200 ${
isAnimating ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
<div className={`relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full mx-4 p-6 ${
isClosing ? 'animate-scale-out' : isAnimating ? 'animate-scale-in' : 'opacity-0 scale-95'
}`}>
{/* 关闭按钮 */}
<button

View File

@@ -315,6 +315,26 @@
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes scaleOut {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0.95);
opacity: 0;
}
}
.animate-fade-in {
animation: fadeIn 0.2s ease-out forwards;
}
@@ -323,6 +343,14 @@
animation: scaleIn 0.3s ease-out forwards;
}
.animate-fade-out {
animation: fadeOut 0.2s ease-in forwards;
}
.animate-scale-out {
animation: scaleOut 0.3s ease-in forwards;
}
/* 游戏字体 - 使用系统字体 */
.font-game {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

View File

@@ -40,6 +40,7 @@ const GameDeploymentPage: React.FC = () => {
const [games, setGames] = useState<Games>({})
const [loading, setLoading] = useState(true)
const [showInstallModal, setShowInstallModal] = useState(false)
const [installModalAnimating, setInstallModalAnimating] = useState(false)
const [selectedGame, setSelectedGame] = useState<{ key: string; info: GameInfo } | null>(null)
const [installPath, setInstallPath] = useState('')
const [installing, setInstalling] = useState(false)
@@ -64,6 +65,7 @@ const GameDeploymentPage: React.FC = () => {
const [downloadComplete, setDownloadComplete] = useState(false)
const [downloadResult, setDownloadResult] = useState<any>(null)
const [showCreateInstanceModal, setShowCreateInstanceModal] = useState(false)
const [createInstanceModalAnimating, setCreateInstanceModalAnimating] = useState(false)
const [instanceName, setInstanceName] = useState('')
const [instanceDescription, setInstanceDescription] = useState('')
const [creatingInstance, setCreatingInstance] = useState(false)
@@ -544,7 +546,7 @@ const GameDeploymentPage: React.FC = () => {
message: `Minecraft实例 "${instanceName}" 创建成功!`
})
setShowCreateInstanceModal(false)
handleCloseCreateInstanceModal()
// 重置表单
setSelectedCategory('')
@@ -609,6 +611,23 @@ const GameDeploymentPage: React.FC = () => {
setInstanceName(gameInfo.game_nameCN)
setInstallPath('')
setShowInstallModal(true)
setTimeout(() => setInstallModalAnimating(true), 10)
}
// 关闭安装对话框
const handleCloseInstallModal = () => {
setInstallModalAnimating(false)
setTimeout(() => {
setShowInstallModal(false)
}, 300)
}
// 关闭创建实例对话框
const handleCloseCreateInstanceModal = () => {
setCreateInstanceModalAnimating(false)
setTimeout(() => {
setShowCreateInstanceModal(false)
}, 300)
}
// 开始安装游戏
@@ -642,7 +661,7 @@ const GameDeploymentPage: React.FC = () => {
const fullCommand = `steamcmd +${loginCommand} +${installCommand} +quit`
// 关闭对话框
setShowInstallModal(false)
handleCloseInstallModal()
// 调用后端API开始游戏安装
const response = await apiClient.installGame({
@@ -1076,6 +1095,7 @@ const GameDeploymentPage: React.FC = () => {
setInstanceName(`${selectedServer}-${selectedVersion}`)
setInstanceDescription(`Minecraft ${selectedServer} ${selectedVersion} 服务器`)
setShowCreateInstanceModal(true)
setTimeout(() => setCreateInstanceModalAnimating(true), 10)
}}
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors flex items-center justify-center space-x-2"
>
@@ -1264,14 +1284,18 @@ const GameDeploymentPage: React.FC = () => {
{/* 安装配置对话框 */}
{showInstallModal && selectedGame && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 animate-fade-in">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md mx-4 transform transition-all duration-300 animate-scale-in">
<div className={`fixed inset-0 bg-black/50 flex items-center justify-center z-50 transition-opacity duration-300 ${
installModalAnimating ? 'opacity-100' : 'opacity-0'
}`}>
<div className={`bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md mx-4 transform transition-all duration-300 ${
installModalAnimating ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
}`}>
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{selectedGame.info.game_nameCN}
</h3>
<button
onClick={() => setShowInstallModal(false)}
onClick={handleCloseInstallModal}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<X className="w-5 h-5" />
@@ -1380,7 +1404,7 @@ const GameDeploymentPage: React.FC = () => {
<div className="flex justify-end space-x-3 p-6 border-t border-gray-200 dark:border-gray-700">
<button
onClick={() => setShowInstallModal(false)}
onClick={handleCloseInstallModal}
className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-500 transition-colors"
>
@@ -1404,14 +1428,18 @@ const GameDeploymentPage: React.FC = () => {
{/* 创建Minecraft实例对话框 */}
{showCreateInstanceModal && downloadResult && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 animate-fade-in">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md mx-4 transform transition-all duration-300 animate-scale-in">
<div className={`fixed inset-0 bg-black/50 flex items-center justify-center z-50 transition-opacity duration-300 ${
createInstanceModalAnimating ? 'opacity-100' : 'opacity-0'
}`}>
<div className={`bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md mx-4 transform transition-all duration-300 ${
createInstanceModalAnimating ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
}`}>
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Minecraft实例
</h3>
<button
onClick={() => setShowCreateInstanceModal(false)}
onClick={handleCloseCreateInstanceModal}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<X className="w-5 h-5" />
@@ -1463,7 +1491,7 @@ const GameDeploymentPage: React.FC = () => {
<div className="flex space-x-3 p-6 border-t border-gray-200 dark:border-gray-700">
<button
onClick={() => setShowCreateInstanceModal(false)}
onClick={handleCloseCreateInstanceModal}
className="flex-1 px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-500 transition-colors"
>

View File

@@ -50,7 +50,9 @@ const InstanceManagerPage: React.FC = () => {
const [loading, setLoading] = useState(true)
const [marketLoading, setMarketLoading] = useState(false)
const [showCreateModal, setShowCreateModal] = useState(false)
const [createModalAnimating, setCreateModalAnimating] = useState(false)
const [showInstallModal, setShowInstallModal] = useState(false)
const [installModalAnimating, setInstallModalAnimating] = useState(false)
const [selectedMarketInstance, setSelectedMarketInstance] = useState<MarketInstance | null>(null)
const [editingInstance, setEditingInstance] = useState<Instance | null>(null)
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
@@ -125,8 +127,7 @@ const InstanceManagerPage: React.FC = () => {
title: '创建成功',
message: `实例 "${formData.name}" 已创建`
})
setShowCreateModal(false)
resetForm()
handleCloseCreateModal()
fetchInstances()
}
} catch (error: any) {
@@ -160,8 +161,7 @@ const InstanceManagerPage: React.FC = () => {
title: '更新成功',
message: `实例 "${formData.name}" 已更新`
})
setEditingInstance(null)
resetForm()
handleCloseCreateModal()
fetchInstances()
}
} catch (error: any) {
@@ -214,9 +214,7 @@ const InstanceManagerPage: React.FC = () => {
title: '安装成功',
message: `实例 "${selectedMarketInstance.name}" 已安装`
})
setShowInstallModal(false)
setSelectedMarketInstance(null)
setInstallFormData({ workingDirectory: '' })
handleCloseInstallModal()
fetchInstances()
setActiveTab('instances')
}
@@ -242,6 +240,7 @@ const InstanceManagerPage: React.FC = () => {
const handleOpenInstallModal = (marketInstance: MarketInstance) => {
setSelectedMarketInstance(marketInstance)
setShowInstallModal(true)
setTimeout(() => setInstallModalAnimating(true), 10)
}
// 启动实例
@@ -420,6 +419,26 @@ const InstanceManagerPage: React.FC = () => {
})
}
// 关闭创建/编辑模态框
const handleCloseCreateModal = () => {
setCreateModalAnimating(false)
setTimeout(() => {
setShowCreateModal(false)
setEditingInstance(null)
resetForm()
}, 300)
}
// 关闭安装模态框
const handleCloseInstallModal = () => {
setInstallModalAnimating(false)
setTimeout(() => {
setShowInstallModal(false)
setSelectedMarketInstance(null)
setInstallFormData({ workingDirectory: '' })
}, 300)
}
// 编辑实例
const handleEditInstance = (instance: Instance) => {
setEditingInstance(instance)
@@ -432,6 +451,7 @@ const InstanceManagerPage: React.FC = () => {
stopCommand: instance.stopCommand
})
setShowCreateModal(true)
setTimeout(() => setCreateModalAnimating(true), 10)
}
// 获取状态图标
@@ -488,6 +508,7 @@ const InstanceManagerPage: React.FC = () => {
setEditingInstance(null)
resetForm()
setShowCreateModal(true)
setTimeout(() => setCreateModalAnimating(true), 10)
}}
className="flex items-center space-x-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
@@ -546,6 +567,7 @@ const InstanceManagerPage: React.FC = () => {
setEditingInstance(null)
resetForm()
setShowCreateModal(true)
setTimeout(() => setCreateModalAnimating(true), 10)
}}
className="inline-flex items-center space-x-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
@@ -742,8 +764,12 @@ const InstanceManagerPage: React.FC = () => {
{/* 创建/编辑实例模态框 */}
{showCreateModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 animate-fade-in">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md mx-4 max-h-[90vh] overflow-y-auto transform transition-all duration-300 animate-scale-in">
<div className={`fixed inset-0 z-50 flex items-center justify-center bg-black/50 transition-opacity duration-300 ${
createModalAnimating ? 'opacity-100' : 'opacity-0'
}`}>
<div className={`bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md mx-4 max-h-[90vh] overflow-y-auto transform transition-all duration-300 ${
createModalAnimating ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
}`}>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
{editingInstance ? '编辑实例' : '创建实例'}
</h2>
@@ -832,11 +858,7 @@ const InstanceManagerPage: React.FC = () => {
<div className="flex items-center justify-end space-x-3 mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<button
onClick={() => {
setShowCreateModal(false)
setEditingInstance(null)
resetForm()
}}
onClick={handleCloseCreateModal}
className="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
>
@@ -855,8 +877,12 @@ const InstanceManagerPage: React.FC = () => {
{/* 安装实例模态框 */}
{showInstallModal && selectedMarketInstance && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 animate-fade-in">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md mx-4 transform transition-all duration-300 animate-scale-in">
<div className={`fixed inset-0 z-50 flex items-center justify-center bg-black/50 transition-opacity duration-300 ${
installModalAnimating ? 'opacity-100' : 'opacity-0'
}`}>
<div className={`bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md mx-4 transform transition-all duration-300 ${
installModalAnimating ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
}`}>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
: {selectedMarketInstance.name}
</h2>
@@ -914,11 +940,7 @@ const InstanceManagerPage: React.FC = () => {
<div className="flex items-center justify-end space-x-3 mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<button
onClick={() => {
setShowInstallModal(false)
setSelectedMarketInstance(null)
setInstallFormData({ workingDirectory: '' })
}}
onClick={handleCloseInstallModal}
className="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
>

View File

@@ -43,6 +43,7 @@ const ScheduledTasksPage: React.FC = () => {
const [instances, setInstances] = useState<Instance[]>([])
const [loading, setLoading] = useState(true)
const [showModal, setShowModal] = useState(false)
const [modalAnimating, setModalAnimating] = useState(false)
const [editingTask, setEditingTask] = useState<ScheduledTask | null>(null)
const [formData, setFormData] = useState({
name: '',
@@ -160,9 +161,7 @@ const ScheduledTasksPage: React.FC = () => {
}
}
setShowModal(false)
setEditingTask(null)
resetForm()
handleCloseModal()
fetchTasks()
} catch (error: any) {
console.error('保存定时任务失败:', error)
@@ -186,6 +185,7 @@ const ScheduledTasksPage: React.FC = () => {
enabled: task.enabled
})
setShowModal(true)
setTimeout(() => setModalAnimating(true), 10)
}
const handleDelete = async (taskId: string) => {
@@ -251,9 +251,12 @@ const ScheduledTasksPage: React.FC = () => {
}
const handleCloseModal = () => {
setShowModal(false)
setEditingTask(null)
resetForm()
setModalAnimating(false)
setTimeout(() => {
setShowModal(false)
setEditingTask(null)
resetForm()
}, 300)
}
const getActionIcon = (action: string) => {
@@ -308,7 +311,10 @@ const ScheduledTasksPage: React.FC = () => {
</div>
</div>
<button
onClick={() => setShowModal(true)}
onClick={() => {
setShowModal(true)
setTimeout(() => setModalAnimating(true), 10)
}}
className="flex items-center space-x-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
<Plus className="w-4 h-4" />
@@ -325,7 +331,10 @@ const ScheduledTasksPage: React.FC = () => {
<h3 className="text-lg font-medium text-black dark:text-white mb-2"></h3>
<p className="text-gray-600 dark:text-gray-400 mb-4"></p>
<button
onClick={() => setShowModal(true)}
onClick={() => {
setShowModal(true)
setTimeout(() => setModalAnimating(true), 10)
}}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
@@ -390,8 +399,12 @@ const ScheduledTasksPage: React.FC = () => {
{/* 新建/编辑任务模态框 */}
{showModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 animate-fade-in">
<div className="w-full max-w-md glass rounded-lg border border-white/20 dark:border-gray-700/30 transform transition-all duration-300 animate-scale-in">
<div className={`fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 transition-opacity duration-300 ${
modalAnimating ? 'opacity-100' : 'opacity-0'
}`}>
<div className={`w-full max-w-md glass rounded-lg border border-white/20 dark:border-gray-700/30 transform transition-all duration-300 ${
modalAnimating ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
}`}>
<div className="p-6">
<h2 className="text-xl font-bold text-black dark:text-white mb-4">
{editingTask ? '编辑任务' : '新建任务'}