TG Bot 命令交互 + UI 圆角色差深度优化 + 通知配置修复

## TG Bot 交互命令系统 (新增)
- telegram_bot.go: 命令路由 + /start /help /status /search /downloads /stats
- telegram_webhook.go: Webhook 接收 + Polling 管理端点
- Polling 模式: 无需公网 HTTPS, 服务器启动自动轮询
- HTML parse_mode 统一消息格式

## 通知配置 BUG 修复
- validateChannel 添加 email 类型支持
- 前后端字段统一: channel_type -> type
- ListByType 新增仓库方法

## UI 深度优化
- glass-panel: 添加 rounded-2xl + 可见边框 + padding
- card: 添加 p-4 sm:p-6
- 全局 rounded-md -> rounded-xl, 裸 rounded -> rounded-lg
- 500+ 深色硬编码类名清零
- text-gray-400 -> gray-500 色差提升
- 按钮组 flex-wrap 防溢出
- data-table 表格溢出截断
This commit is contained in:
ShukeBta
2026-05-28 08:35:59 +08:00
parent c094818594
commit 5baf9515ea
46 changed files with 872 additions and 201 deletions
+8 -8
View File
@@ -40,7 +40,7 @@ export function APIConfigsPanel() {
{!loading && (
<div className="glass-panel overflow-hidden">
<table className="w-full text-left text-sm">
<thead className="border-b border-white/5 text-xs uppercase tracking-wider text-sand-500">
<thead className="border-b border-gray-200 text-xs uppercase tracking-wider text-sand-500">
<tr>
<th className="px-4 py-3">服务</th>
<th className="px-4 py-3">密钥</th>
@@ -63,7 +63,7 @@ export function APIConfigsPanel() {
) : (
<tr
key={item.id}
className="border-t border-white/5 transition hover:bg-white/[0.02]"
className="border-t border-gray-200 transition hover:bg-gray-50"
>
<td className="px-4 py-3">
<p className="font-medium text-ink-600">{item.provider}</p>
@@ -75,7 +75,7 @@ export function APIConfigsPanel() {
{item.has_key ? (
<span className="text-brand-500">{item.masked_key}</span>
) : (
<span className="text-sand-400">未配置</span>
<span className="text-gray-500">未配置</span>
)}
</td>
<td className="px-4 py-3">
@@ -93,7 +93,7 @@ export function APIConfigsPanel() {
<div className="flex items-center justify-end gap-1">
<button
onClick={() => setEditing(item.provider)}
className="rounded p-1.5 text-ink-50 transition hover:bg-white/5 hover:text-white"
className="rounded-lg p-1.5 text-ink-50 transition hover:bg-gray-50 hover:text-white"
title="编辑"
>
<Save size={14} />
@@ -104,7 +104,7 @@ export function APIConfigsPanel() {
`已配置 ${item.has_key ? '✓' : '✗'} 密钥 (在线测试请用对应功能页面)`,
)
}}
className="rounded p-1.5 text-ink-50 transition hover:bg-white/5 hover:text-ink-200"
className="rounded-lg p-1.5 text-ink-50 transition hover:bg-gray-50 hover:text-ink-200"
title="查看状态"
>
<Eye size={14} />
@@ -117,7 +117,7 @@ export function APIConfigsPanel() {
toast.success('已清除')
refresh()
}}
className="rounded p-1.5 text-ink-50 transition hover:bg-red-400/10 hover:text-red-400"
className="rounded-lg p-1.5 text-ink-50 transition hover:bg-red-400/10 hover:text-red-400"
title="清除密钥"
>
<Trash2 size={14} />
@@ -170,7 +170,7 @@ function EditingRow({
}
return (
<tr className="border-t border-white/5 bg-primary-400/5">
<tr className="border-t border-gray-200 bg-primary-400/5">
<td colSpan={4} className="px-4 py-3">
<form onSubmit={submit} className="flex flex-wrap items-end gap-3">
<span className="text-sm font-medium text-ink-600">{item.provider}</span>
@@ -207,7 +207,7 @@ function EditingRow({
<button
type="button"
onClick={onCancel}
className="rounded border border-sand-400/30 px-2 py-1.5 text-xs text-ink-50 hover:text-white"
className="rounded-lg border border-sand-400/30 px-2 py-1.5 text-xs text-ink-50 hover:text-white"
>
<X size={12} />
</button>
+1 -1
View File
@@ -16,7 +16,7 @@ export function AppFooter({ className = '' }: { className?: string }) {
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-xs text-sand-500 transition-colors hover:bg-sand-200 hover:text-ink-50"
className="flex items-center gap-1.5 rounded-xl px-2 py-1.5 text-xs text-sand-500 transition-colors hover:bg-sand-200 hover:text-ink-50"
title={link.label}
>
<link.icon size={14} />
+14 -14
View File
@@ -78,7 +78,7 @@ export function Layout() {
{/* Toggle Collapse Button for Large Screen */}
<button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="rounded-lg p-1.5 text-gray-400 hover:bg-gray-100 hover:text-gray-900 transition-colors hidden lg:block"
className="rounded-xl p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-900 transition-colors hidden lg:block"
>
<Menu size={18} />
</button>
@@ -86,7 +86,7 @@ export function Layout() {
{/* Mobile Drawer Close Button */}
<button
onClick={() => setIsMobileDrawerOpen(false)}
className="rounded-lg p-1.5 text-gray-400 hover:bg-gray-100 hover:text-gray-900 transition-colors block lg:hidden"
className="rounded-xl p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-900 transition-colors block lg:hidden"
>
<X size={18} />
</button>
@@ -114,7 +114,7 @@ export function Layout() {
<SectionHeader label="媒体片库" visible={isSidebarOpen || isMobileDrawerOpen} />
<div className="space-y-1">
{libraries.length === 0 && (isSidebarOpen || isMobileDrawerOpen) && (
<div className="px-4 py-2 text-xs text-gray-400 italic">暂无媒体片库</div>
<div className="px-4 py-2 text-xs text-gray-500 italic">暂无媒体片库</div>
)}
{libraries.map((lib) => (
<SidebarLink
@@ -180,7 +180,7 @@ export function Layout() {
onClick={() => { logout(); navigate('/login') }}
className={clsx(
"flex items-center gap-3.5 rounded-xl px-4 py-3 text-sm font-semibold transition-all duration-300 w-full group/logout",
(isSidebarOpen || isMobileDrawerOpen) ? "justify-start text-gray-500 hover:bg-red-50 hover:text-red-600" : "justify-center text-gray-400 hover:text-red-600"
(isSidebarOpen || isMobileDrawerOpen) ? "justify-start text-gray-500 hover:bg-red-50 hover:text-red-600" : "justify-center text-gray-500 hover:text-red-600"
)}
title={`安全登出 (${user?.username})`}
>
@@ -248,7 +248,7 @@ export function Layout() {
<form onSubmit={handleSearchSubmit} className="relative w-full hidden sm:block">
<span className={clsx(
"absolute left-4 top-1/2 -translate-y-1/2 transition-colors duration-200",
searchFocused ? "text-brand-600" : "text-gray-400"
searchFocused ? "text-brand-600" : "text-gray-500"
)}>
<Search size={16} />
</span>
@@ -259,10 +259,10 @@ export function Layout() {
onFocus={() => setSearchFocused(true)}
onBlur={() => setSearchFocused(false)}
placeholder="搜索电影、电视剧、演员、种子站点..."
className="w-full rounded-full border border-gray-200 bg-gray-50/50 py-2.5 pl-11 pr-12 text-sm text-gray-900 placeholder-gray-400 outline-none transition-all duration-300 focus:border-brand-500 focus:bg-white focus:ring-4 focus:ring-brand-100/40"
className="w-full rounded-full border border-gray-200 bg-gray-50/50 py-2.5 pl-11 pr-12 text-sm text-gray-900 placeholder-gray-500 outline-none transition-all duration-300 focus:border-brand-500 focus:bg-white focus:ring-4 focus:ring-brand-100/40"
/>
<div className="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none">
<span className="rounded-md border border-gray-200 bg-white px-1.5 py-0.5 text-[9px] font-bold text-gray-400 uppercase tracking-wider">
<span className="rounded-xl border border-gray-200 bg-white px-1.5 py-0.5 text-[9px] font-bold text-gray-500 uppercase tracking-wider">
Enter
</span>
</div>
@@ -274,7 +274,7 @@ export function Layout() {
{/* Quick search button shown ONLY on Mobile screens */}
<Link
to="/search"
className="rounded-xl border border-gray-200 p-2.5 text-gray-400 hover:bg-gray-100 hover:text-gray-900 sm:hidden"
className="rounded-xl border border-gray-200 p-2.5 text-gray-500 hover:bg-gray-100 hover:text-gray-900 sm:hidden"
>
<Search size={18} />
</Link>
@@ -289,7 +289,7 @@ export function Layout() {
</Link>
{/* Notification alert bubble */}
<button className="relative rounded-xl border border-gray-200 p-2.5 text-gray-400 hover:bg-gray-100 hover:text-gray-900 transition-all">
<button className="relative rounded-xl border border-gray-200 p-2.5 text-gray-500 hover:bg-gray-100 hover:text-gray-900 transition-all">
<Bell size={18} />
<span className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-brand-500 ring-2 ring-white animate-pulse" />
</button>
@@ -310,7 +310,7 @@ export function Layout() {
<p className="text-xs font-bold text-gray-900 leading-none">{user?.username}</p>
<p className="text-[9px] text-gray-500 font-bold uppercase tracking-wider mt-0.5 leading-none">{user?.role}</p>
</div>
<ChevronDown size={14} className="text-gray-400" />
<ChevronDown size={14} className="text-gray-500" />
</button>
<AnimatePresence>
@@ -389,7 +389,7 @@ export function Layout() {
</main>
{/* Absolute Footer Frame */}
<AppFooter className="border-t border-gray-200/50 bg-white py-5 text-center text-xs text-gray-400" />
<AppFooter className="border-t border-gray-200/50 bg-white py-5 text-center text-xs text-gray-500" />
</div>
</div>
)
@@ -399,7 +399,7 @@ function SectionHeader({ label, visible }: { label: string; visible: boolean })
if (!visible) return <div className="h-5" />;
return (
<div className="px-4 mb-2 mt-4">
<span className="text-[10px] font-bold uppercase tracking-[0.25em] text-gray-400">
<span className="text-[10px] font-bold uppercase tracking-[0.25em] text-gray-500">
{label}
</span>
</div>
@@ -432,7 +432,7 @@ function SidebarLink({ to, icon, label, end, collapsed }: SidebarLinkProps) {
<>
<span className={clsx(
"flex shrink-0 items-center justify-center w-5 h-5 transition-transform duration-300 group-hover:scale-110",
isActive ? "text-[#c9954a]" : "text-gray-400 group-hover:text-gray-700"
isActive ? "text-[#c9954a]" : "text-gray-500 group-hover:text-gray-700"
)}>
{icon}
</span>
@@ -446,7 +446,7 @@ function SidebarLink({ to, icon, label, end, collapsed }: SidebarLinkProps) {
</motion.span>
)}
{collapsed && (
<div className="absolute left-full ml-3 px-2.5 py-1.5 rounded-lg bg-gray-900 text-white text-xs font-semibold opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity shadow-lg z-50 whitespace-nowrap">
<div className="absolute left-full ml-3 px-2.5 py-1.5 rounded-xl bg-gray-900 text-white text-xs font-semibold opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity shadow-lg z-50 whitespace-nowrap">
{label}
</div>
)}
+5 -5
View File
@@ -32,7 +32,7 @@ export const MediaCard = ({
referrerPolicy="no-referrer"
/>
) : (
<div className="flex h-full w-full flex-col items-center justify-center gap-2 text-gray-400 bg-gray-50">
<div className="flex h-full w-full flex-col items-center justify-center gap-2 text-gray-500 bg-gray-50">
<Film size={28} className="stroke-[1.5]" />
<span className="text-[10px] uppercase tracking-wider font-bold">No Poster</span>
</div>
@@ -40,7 +40,7 @@ export const MediaCard = ({
{/* Episode count badge */}
{count !== undefined && count > 1 && (
<span className="absolute right-3 top-3 inline-flex items-center gap-1 rounded-lg bg-[#111827]/90 px-2 py-1 text-[10px] font-bold text-white shadow-sm border border-white/10">
<span className="absolute right-3 top-3 inline-flex items-center gap-1 rounded-xl bg-[#111827]/90 px-2 py-1 text-[10px] font-bold text-white shadow-sm border border-gray-200">
<Layers size={10} className="text-[#c9954a]" />
<span>{count} 集</span>
</span>
@@ -48,7 +48,7 @@ export const MediaCard = ({
{/* Rating Badge */}
{(rating || (media as any).rating) && (
<span className="absolute left-3 top-3 inline-flex items-center gap-0.5 rounded-lg bg-[#111827]/90 px-2 py-1 text-[10px] font-bold text-[#c9954a] shadow-sm border border-white/10">
<span className="absolute left-3 top-3 inline-flex items-center gap-0.5 rounded-xl bg-[#111827]/90 px-2 py-1 text-[10px] font-bold text-[#c9954a] shadow-sm border border-gray-200">
<Star size={10} fill="currentColor" />
<span>{(rating || (media as any).rating).toFixed(1)}</span>
</span>
@@ -66,7 +66,7 @@ export const MediaCard = ({
<Play size={10} fill="currentColor" className="text-white" />
<span>立即观影</span>
</span>
<p className="text-[10px] text-gray-300 font-semibold line-clamp-2 leading-relaxed">
<p className="text-[10px] text-gray-500 font-semibold line-clamp-2 leading-relaxed">
{media.overview || "暂无简介内容"}
</p>
</motion.div>
@@ -91,7 +91,7 @@ export const MediaCard = ({
<div className="flex items-center justify-between text-[11px] text-gray-500 font-bold uppercase tracking-wider">
<span>{media.year > 0 ? media.year : "未知年份"}</span>
{media.video_codec && (
<span className="px-1.5 py-0.5 rounded-md bg-gray-50 text-gray-600 border border-gray-100">
<span className="px-1.5 py-0.5 rounded-xl bg-gray-50 text-gray-600 border border-gray-100">
{media.video_codec}
</span>
)}