mirror of
https://github.com/NanmiCoder/MediaCrawler.git
synced 2026-10-02 03:38:30 +08:00
refactor: 将 WebUI 源码纳入仓库,移除打包产物
- 新增 webui/ 前端源码目录(React+Vite+TypeScript+Tailwind) - 删除 api/webui/ 中旧的打包静态资源 - 更新 .gitignore 忽略 api/webui/ 构建输出和 webui/node_modules - 更新 README 增加前端构建说明
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Sparkles, Heart } from 'lucide-react'
|
||||
|
||||
export function AuthorFooter() {
|
||||
const { t } = useTranslation('license')
|
||||
|
||||
return (
|
||||
<footer className="h-24 flex-shrink-0 glass-panel border-t border-cyber-border-subtle">
|
||||
<div className="h-full px-6 flex items-center justify-center gap-6">
|
||||
{/* Author Avatar */}
|
||||
<div className="w-14 h-14 rounded-lg overflow-hidden border-2 border-cyber-neon-cyan/60 flex-shrink-0 shadow-glow-cyan-sm">
|
||||
<img
|
||||
src="/logos/my_logo.png"
|
||||
alt="程序员阿江-Relakkes"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Author Info */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg font-bold text-cyber-text-primary">
|
||||
{t('author.name')}
|
||||
</span>
|
||||
<Sparkles className="w-5 h-5 text-cyber-neon-cyan animate-pulse" />
|
||||
</div>
|
||||
<span className="text-sm text-cyber-text-muted hidden sm:inline">
|
||||
{t('author.description')}
|
||||
</span>
|
||||
<div className="flex items-center gap-2 text-cyber-neon-cyan">
|
||||
<Heart className="w-4 h-4 fill-current animate-pulse" />
|
||||
<span className="text-sm font-medium">
|
||||
{t('author.slogan')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Social Links */}
|
||||
<div className="flex items-center gap-3">
|
||||
<a
|
||||
href="https://github.com/NanmiCoder"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-11 h-11 rounded-lg flex items-center justify-center border border-cyber-border-subtle hover:border-cyber-neon-cyan hover:shadow-glow-cyan-sm transition-all bg-cyber-bg-tertiary hover:scale-110"
|
||||
title="GitHub"
|
||||
>
|
||||
<img src="/logos/github.png" alt="GitHub" className="w-6 h-6 object-contain" />
|
||||
</a>
|
||||
<a
|
||||
href="https://space.bilibili.com/434377496"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-11 h-11 rounded-lg flex items-center justify-center border border-cyber-border-subtle hover:border-pink-400 hover:shadow-[0_0_10px_rgba(251,113,133,0.4)] transition-all bg-cyber-bg-tertiary hover:scale-110"
|
||||
title="哔哩哔哩"
|
||||
>
|
||||
<img src="/logos/bilibili_logo.png" alt="Bilibili" className="w-6 h-6 object-contain" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.xiaohongshu.com/user/profile/5f58bd990000000001003753"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-11 h-11 rounded-lg flex items-center justify-center border border-cyber-border-subtle hover:border-red-400 hover:shadow-[0_0_10px_rgba(248,113,113,0.4)] transition-all bg-cyber-bg-tertiary hover:scale-110"
|
||||
title="小红书"
|
||||
>
|
||||
<img src="/logos/xiaohongshu_logo.png" alt="小红书" className="w-6 h-6 object-contain" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.douyin.com/user/MS4wLjABAAAATJPY7LAlaa5X-c8uNdWkvz0jUGgpw4eeXIwu_8BhvqE"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-11 h-11 rounded-lg flex items-center justify-center border border-cyber-border-subtle hover:border-cyber-text-primary hover:shadow-[0_0_10px_rgba(255,255,255,0.3)] transition-all bg-cyber-bg-tertiary hover:scale-110"
|
||||
title="抖音"
|
||||
>
|
||||
<img src="/logos/douyin.png" alt="抖音" className="w-6 h-6 object-contain" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Globe } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
|
||||
const languages = [
|
||||
{ code: 'zh-CN', label: '中文' },
|
||||
{ code: 'en-US', label: 'EN' },
|
||||
]
|
||||
|
||||
export function LanguageSwitch() {
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const currentLang = languages.find(l => l.code === i18n.language) || languages[0]
|
||||
|
||||
return (
|
||||
<Select value={i18n.language} onValueChange={(lang) => i18n.changeLanguage(lang)}>
|
||||
<SelectTrigger className="w-20 h-7 text-xs font-mono border-cyber-border-subtle bg-cyber-bg-tertiary/50 hover:border-cyber-neon-cyan/50 transition-colors">
|
||||
<Globe className="w-3 h-3 mr-1 text-cyber-text-secondary" />
|
||||
<SelectValue>{currentLang.label}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{languages.map((lang) => (
|
||||
<SelectItem key={lang.code} value={lang.code} className="text-xs font-mono">
|
||||
{lang.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Terminal } from '@/components/console/Terminal'
|
||||
import { useLogWebSocket } from '@/hooks/useWebSocket'
|
||||
|
||||
export function MainContent() {
|
||||
// Connect to WebSocket for logs
|
||||
useLogWebSocket()
|
||||
|
||||
return (
|
||||
<main className="flex-1 flex flex-col overflow-hidden min-h-0 relative z-10">
|
||||
<Terminal />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Bug, Wifi, AlertTriangle, Github } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { useCrawlerStore } from '@/store/crawlerStore'
|
||||
import { useCrawlerStatus } from '@/hooks/useCrawler'
|
||||
import { LanguageSwitch } from './LanguageSwitch'
|
||||
import { ThemeToggle } from './ThemeToggle'
|
||||
|
||||
interface SidebarProps {
|
||||
onShowDisclaimer?: () => void
|
||||
}
|
||||
|
||||
export function Sidebar({ onShowDisclaimer }: SidebarProps) {
|
||||
const { t } = useTranslation()
|
||||
const { t: tLicense } = useTranslation('license')
|
||||
const status = useCrawlerStore((state) => state.status)
|
||||
|
||||
// Poll status
|
||||
useCrawlerStatus()
|
||||
|
||||
const isRunning = status === 'running'
|
||||
|
||||
return (
|
||||
<header className="h-14 flex-shrink-0 glass-panel border-b border-cyber-border-subtle relative z-10">
|
||||
<div className="h-full px-4 flex items-center justify-between">
|
||||
{/* Left: Logo and GitHub Star */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Bug className="w-5 h-5 text-cyber-neon-cyan" />
|
||||
<span className="font-mono font-bold text-cyber-text-primary tracking-wider text-sm">
|
||||
MediaCrawler
|
||||
</span>
|
||||
<a
|
||||
href="https://github.com/NanmiCoder/MediaCrawler"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-md border border-cyber-border-subtle hover:border-cyber-neon-cyan hover:shadow-glow-cyan-sm transition-all bg-cyber-bg-tertiary"
|
||||
>
|
||||
<Github className="w-4 h-4 text-cyber-text-secondary" />
|
||||
<span className="text-xs font-mono text-cyber-text-secondary">Star</span>
|
||||
</a>
|
||||
{isRunning && (
|
||||
<Badge variant="running" className="text-[10px]">
|
||||
{t('status.active')}
|
||||
</Badge>
|
||||
)}
|
||||
{isRunning && (
|
||||
<span className="w-2 h-2 bg-cyber-neon-green rounded-full shadow-glow-green-sm animate-pulse-fast" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Center: Warning Text */}
|
||||
<button
|
||||
onClick={onShowDisclaimer}
|
||||
className="flex items-center gap-3 px-4 py-1.5 rounded-lg border border-cyber-neon-orange/50 bg-cyber-neon-orange/10 hover:bg-cyber-neon-orange/20 transition-all cursor-pointer"
|
||||
>
|
||||
<AlertTriangle className="w-4 h-4 text-cyber-neon-orange flex-shrink-0" />
|
||||
<div className="flex items-center gap-4 text-xs font-mono">
|
||||
<span className="text-cyber-neon-orange">
|
||||
<span className="text-cyber-neon-pink font-bold">1.</span> {tLicense('content.line1')}
|
||||
</span>
|
||||
<span className="text-cyber-neon-orange">
|
||||
<span className="text-cyber-neon-pink font-bold">2.</span> {tLicense('content.line2')}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Right: Actions and Status */}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Theme Toggle */}
|
||||
<ThemeToggle />
|
||||
{/* Language Switch */}
|
||||
<LanguageSwitch />
|
||||
|
||||
{/* Status Info */}
|
||||
<div className="hidden lg:flex items-center gap-2 text-xs font-mono">
|
||||
<span className="text-cyber-text-muted">{t('sidebar.api')}:</span>
|
||||
<span className="text-cyber-neon-green">v1.0.0</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Wifi className="w-3 h-3 text-cyber-text-secondary" />
|
||||
<span className="text-cyber-text-secondary">{t('sidebar.local')}</span>
|
||||
<span className="status-dot status-dot-online" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Sun, Moon, Monitor } from 'lucide-react'
|
||||
import { useThemeStore } from '@/store/themeStore'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
|
||||
type Theme = 'light' | 'dark' | 'system'
|
||||
|
||||
const themes: { value: Theme; label: string; icon: typeof Sun }[] = [
|
||||
{ value: 'light', label: 'Light', icon: Sun },
|
||||
{ value: 'dark', label: 'Dark', icon: Moon },
|
||||
{ value: 'system', label: 'Auto', icon: Monitor },
|
||||
]
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { theme, setTheme } = useThemeStore()
|
||||
|
||||
const currentTheme = themes.find(t => t.value === theme) || themes[0]
|
||||
const Icon = currentTheme.icon
|
||||
|
||||
return (
|
||||
<Select value={theme} onValueChange={(value: Theme) => setTheme(value)}>
|
||||
<SelectTrigger className="w-20 h-7 text-xs font-mono border-cyber-border-subtle bg-cyber-bg-tertiary/50 hover:border-cyber-neon-cyan/50 transition-colors">
|
||||
<Icon className="w-3 h-3 mr-1 text-cyber-text-secondary" />
|
||||
<SelectValue>{currentTheme.label}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{themes.map(({ value, label, icon: ItemIcon }) => (
|
||||
<SelectItem key={value} value={value} className="text-xs font-mono">
|
||||
<div className="flex items-center gap-2">
|
||||
<ItemIcon className="w-3 h-3" />
|
||||
{label}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user