261 lines
8.9 KiB
TypeScript
261 lines
8.9 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import { Config } from '@/types';
|
||
|
||
interface CommandModalProps {
|
||
config: Config;
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
}
|
||
|
||
export function CommandModal({ config, isOpen, onClose }: CommandModalProps) {
|
||
const [selectedDistro, setSelectedDistro] = useState('');
|
||
const [command, setCommand] = useState('');
|
||
const [loading, setLoading] = useState(false);
|
||
const [copied, setCopied] = useState(false);
|
||
|
||
// 重置状态当模态框关闭时
|
||
useEffect(() => {
|
||
if (!isOpen) {
|
||
setSelectedDistro('');
|
||
setCommand('');
|
||
setCopied(false);
|
||
}
|
||
}, [isOpen]);
|
||
|
||
const distros = config.linuxCommands.map(distro => {
|
||
const dirConfig = config.directories.find(d => d.name === distro);
|
||
return {
|
||
name: distro,
|
||
description: dirConfig?.description || distro,
|
||
icon: dirConfig?.icon || 'fab fa-linux',
|
||
color: dirConfig?.color || '#000000'
|
||
};
|
||
});
|
||
|
||
const generateCommand = async () => {
|
||
if (!selectedDistro) return;
|
||
|
||
setLoading(true);
|
||
try {
|
||
const response = await fetch('/api/command', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ distro: selectedDistro }),
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
setCommand(data.data.command);
|
||
} else {
|
||
console.error('Failed to generate command:', data.error);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error generating command:', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const copyToClipboard = async () => {
|
||
try {
|
||
await navigator.clipboard.writeText(command);
|
||
setCopied(true);
|
||
setTimeout(() => setCopied(false), 2000);
|
||
} catch (error) {
|
||
console.error('Failed to copy to clipboard:', error);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (selectedDistro) {
|
||
generateCommand();
|
||
} else {
|
||
setCommand('');
|
||
}
|
||
}, [selectedDistro]);
|
||
|
||
// 按ESC键关闭模态框
|
||
useEffect(() => {
|
||
const handleEscape = (e: KeyboardEvent) => {
|
||
if (e.key === 'Escape' && isOpen) {
|
||
onClose();
|
||
}
|
||
};
|
||
|
||
if (isOpen) {
|
||
document.addEventListener('keydown', handleEscape);
|
||
document.body.style.overflow = 'hidden'; // 防止背景滚动
|
||
}
|
||
|
||
return () => {
|
||
document.removeEventListener('keydown', handleEscape);
|
||
document.body.style.overflow = 'unset'; // 恢复滚动
|
||
};
|
||
}, [isOpen, onClose]);
|
||
|
||
if (!isOpen) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<div
|
||
className="fixed inset-0 z-50 flex items-center justify-center"
|
||
onClick={onClose}
|
||
>
|
||
{/* 背景遮罩 */}
|
||
<div className="absolute inset-0 bg-black bg-opacity-50 transition-opacity"></div>
|
||
|
||
{/* 模态框内容 */}
|
||
<div
|
||
className="relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto"
|
||
onClick={(e) => e.stopPropagation()} // 防止点击内容区域时关闭
|
||
>
|
||
<div className="p-6">
|
||
{/* 标题和关闭按钮 */}
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-xl font-bold text-gray-800 dark:text-gray-200">
|
||
添加 Linux 镜像源
|
||
</h3>
|
||
<button
|
||
onClick={onClose}
|
||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
||
title="关闭"
|
||
>
|
||
<i className="fas fa-times text-xl"></i>
|
||
</button>
|
||
</div>
|
||
|
||
{/* 发行版选择 */}
|
||
<div className="mb-6">
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
选择您的 Linux 发行版:
|
||
</label>
|
||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||
{distros.map((distro) => (
|
||
<button
|
||
key={distro.name}
|
||
onClick={() => setSelectedDistro(distro.name)}
|
||
className={`p-3 rounded-lg border-2 transition-all ${
|
||
selectedDistro === distro.name
|
||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
||
: 'border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500'
|
||
}`}
|
||
>
|
||
<div className="text-center">
|
||
<i
|
||
className={`${distro.icon} text-2xl mb-1`}
|
||
style={{ color: distro.color }}
|
||
></i>
|
||
<div className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||
{distro.name.charAt(0).toUpperCase() + distro.name.slice(1)}
|
||
</div>
|
||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||
{distro.description}
|
||
</div>
|
||
</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 命令生成结果 */}
|
||
{selectedDistro && (
|
||
<div className="mb-6">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||
生成的命令:
|
||
</label>
|
||
<button
|
||
onClick={copyToClipboard}
|
||
className="px-3 py-1 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors"
|
||
disabled={loading || !command}
|
||
>
|
||
{copied ? (
|
||
<>
|
||
<i className="fas fa-check mr-1"></i>
|
||
已复制
|
||
</>
|
||
) : (
|
||
<>
|
||
<i className="fas fa-copy mr-1"></i>
|
||
复制
|
||
</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
<div className="relative">
|
||
<div className="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm overflow-x-auto">
|
||
{loading ? (
|
||
<div className="flex items-center space-x-2">
|
||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-green-400"></div>
|
||
<span>生成中...</span>
|
||
</div>
|
||
) : command ? (
|
||
<pre className="whitespace-pre-wrap">{command}</pre>
|
||
) : (
|
||
<span>请选择发行版</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 使用说明 */}
|
||
{selectedDistro && command && (
|
||
<div className="mb-6">
|
||
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
使用说明:
|
||
</h4>
|
||
<ol className="list-decimal list-inside space-y-1 text-sm text-gray-600 dark:text-gray-400">
|
||
<li>打开终端</li>
|
||
<li>复制并执行上面的命令</li>
|
||
<li>更新软件包列表:
|
||
<code className="bg-gray-100 dark:bg-gray-700 px-1 rounded">
|
||
{selectedDistro === 'ubuntu' || selectedDistro === 'debian'
|
||
? 'sudo apt update'
|
||
: selectedDistro === 'centos' || selectedDistro === 'fedora'
|
||
? 'sudo dnf update'
|
||
: 'sudo pacman -Syu'
|
||
}
|
||
</code>
|
||
</li>
|
||
<li>验证镜像源是否添加成功</li>
|
||
</ol>
|
||
</div>
|
||
)}
|
||
|
||
{/* 注意事项 */}
|
||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
|
||
<div className="flex items-start space-x-2">
|
||
<i className="fas fa-exclamation-triangle text-yellow-600 dark:text-yellow-400 mt-1"></i>
|
||
<div className="text-sm text-yellow-800 dark:text-yellow-200">
|
||
<p className="font-medium mb-1">注意事项:</p>
|
||
<ul className="list-disc list-inside space-y-1">
|
||
<li>执行命令前请备份原有的软件源配置</li>
|
||
<li>不同的 Linux 发行版使用不同的包管理器</li>
|
||
<li>如果遇到网络问题,请检查 DNS 设置</li>
|
||
<li>某些发行版可能需要额外的依赖包</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 底部按钮 */}
|
||
<div className="flex justify-end pt-4 border-t border-gray-200 dark:border-gray-700">
|
||
<button
|
||
onClick={onClose}
|
||
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
|
||
>
|
||
关闭
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
} |