由于上个仓库主线和分支差距过大且主线不再使用所以新建此仓库

This commit is contained in:
Yakumo Hokori
2025-12-14 02:30:23 +08:00
commit 703d53c6f3
49 changed files with 10806 additions and 0 deletions

View File

@@ -0,0 +1,261 @@
'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>
);
}