feat: 全面UI优化 — 5套主题系统 + 设计体系 + 菜单重组 + 页面优化
1. 主题系统扩展 (5套主题) - ocean-deep(深海蓝) / light-professional(明亮白) / midnight-purple(午夜紫) - pine-green(松林绿) / orange-vitality(活力橙) - useTheme hook 重写支持5主题 + 向后兼容 - AppearanceSettings 5主题预览卡片 2. 设计体系 (index.css) - 按钮系统: dg-btn-primary/secondary/ghost/danger + 尺寸变体 - 徽章系统: dg-badge-* (8色) - 卡片/表格: dg-card-section, dg-table-row, dg-table-header - 状态点: dg-status-dot-* (4色) - 动效关键帧: slideInLeft, scaleIn 3. 菜单重组 (menu.ts) - 5分组: 概览/数据源/工单/权限/系统 - 6个图标更新: Code2/FileEdit/ShieldCheck/FolderLock/ScrollText/Timer - 新增 permission 权限码字段 4. Layout 侧边栏增强 - 左侧渐变指示条 + 发光效果 - hover 微动画 + 图标变色 - 面包屑导航头部 - 主题切换循环5主题 5. 核心页面优化 - TicketsPage: 设计系统类 + 颜色对齐 - QueriesPage: 去Card依赖 + dg-table/dg-btn统一 - Breadcrumb组件 + useBreadcrumb hook
This commit is contained in:
1 parent
4e2df4d3d7
commit
746a000e8a
25 files changed
+7718
-178
No files matched your search
@@ -7,7 +7,7 @@ import {
|
||||
KeyRound, FolderOpen, Server, Zap, ClipboardPaste,
|
||||
History, PanelRightClose, PanelRightOpen,
|
||||
} from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
// Card components replaced by design system utility classes
|
||||
import { useActionPermission } from '@/hooks/useAuth';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
import SchemaSidebar from '@/components/SchemaSidebar';
|
||||
@@ -321,28 +321,24 @@ export default function QueriesPage() {
|
||||
<div className="flex items-center gap-2">
|
||||
{instanceId && (
|
||||
<button onClick={() => setShowSidebar(!showSidebar)}
|
||||
className={`flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium rounded-lg border transition-all ${
|
||||
showSidebar ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : 'text-[var(--dg-text-secondary)] bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]'
|
||||
}`}>
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${showSidebar ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : ''}`}>
|
||||
<Database className="w-3.5 h-3.5" />
|
||||
{showSidebar ? '隐藏' : '导航'}
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => setShowTemplates(!showTemplates)}
|
||||
className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium bg-[var(--dg-bg-tertiary)] hover:bg-[var(--dg-bg-hover)] border border-[var(--dg-border)] rounded-lg transition-all">
|
||||
className="dg-btn dg-btn-ghost dg-btn-sm">
|
||||
<Sparkles className="w-3.5 h-3.5 text-blue-400" />
|
||||
模板 ({templates.length})
|
||||
<ChevronDown className={`w-3 h-3 transition-transform ${showTemplates ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
<button onClick={() => setShowHistory(!showHistory)}
|
||||
className={`flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium rounded-lg border transition-all ${
|
||||
showHistory ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : 'text-[var(--dg-text-secondary)] bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]'
|
||||
}`}>
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${showHistory ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : ''}`}>
|
||||
{showHistory ? <PanelRightClose className="w-3.5 h-3.5" /> : <PanelRightOpen className="w-3.5 h-3.5" />}
|
||||
历史 {history.length > 0 && <span className="text-[10px] px-1 py-0.5 rounded-full bg-[var(--dg-bg-primary)]">{history.length}</span>}
|
||||
</button>
|
||||
<button onClick={handleExecute} disabled={executing || !instanceId}
|
||||
className="flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg text-sm font-medium text-white hover:opacity-90 disabled:opacity-40 transition-all">
|
||||
className="dg-btn dg-btn-primary">
|
||||
{executing ? <Loader2 className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4" />}
|
||||
{executing ? '执行中...' : '执行'}
|
||||
</button>
|
||||
@@ -351,37 +347,35 @@ export default function QueriesPage() {
|
||||
|
||||
{/* -- 模板选择器 -- */}
|
||||
{showTemplates && (
|
||||
<div className="p-3 bg-[var(--dg-bg-secondary)] rounded-lg border border-[var(--dg-border)]">
|
||||
<div className="dg-card p-3">
|
||||
<div className="flex items-center gap-2 mb-3 flex-wrap">
|
||||
{templateCategories.map(cat => (
|
||||
<button key={cat} onClick={() => setTemplateCategory(cat)}
|
||||
className={`px-2.5 py-1 text-[11px] rounded-md transition-all font-medium ${
|
||||
templateCategory === cat ? 'bg-blue-500/15 text-blue-400 border border-blue-500/30' : 'text-[var(--dg-text-muted)] hover:text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] border border-transparent'
|
||||
}`}>
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${templateCategory === cat ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : ''}`}>
|
||||
{catLabel[cat] || cat}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex-1" />
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 text-[var(--dg-text-muted)]" />
|
||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 dg-text-muted" />
|
||||
<input value={templateSearch} onChange={e => setTemplateSearch(e.target.value)}
|
||||
className="pl-7 pr-3 py-1 bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-md text-xs focus:border-blue-400 focus:outline-none w-40"
|
||||
placeholder="搜索模板..." />
|
||||
</div>
|
||||
</div>
|
||||
{filteredTemplates.length === 0 ? (
|
||||
<div className="text-center py-4 text-xs text-[var(--dg-text-muted)]">无匹配模板</div>
|
||||
<div className="text-center py-4 text-xs dg-text-muted">无匹配模板</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 max-h-48 overflow-y-auto">
|
||||
{filteredTemplates.map(tpl => (
|
||||
<button key={tpl.id} onClick={() => handleApplyTemplate(tpl)}
|
||||
className="text-left p-2.5 rounded-lg bg-[var(--dg-bg-primary)] hover:bg-[var(--dg-bg-hover)] transition-colors group border border-[var(--dg-border)] hover:border-[var(--dg-text-muted)]"
|
||||
className="dg-card text-left p-2.5 hover:bg-[var(--dg-bg-hover)] group"
|
||||
title={tpl.description}>
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-400 font-medium">{tpl.category}</span>
|
||||
<span className="text-sm font-medium group-hover:text-blue-400 transition-colors truncate">{tpl.name}</span>
|
||||
</div>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] line-clamp-1">{tpl.description}</p>
|
||||
<p className="text-xs dg-text-muted line-clamp-1">{tpl.description}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -405,7 +399,7 @@ export default function QueriesPage() {
|
||||
<SqlEditor value={sql} onChange={setSql} placeholder={placeholderMap[dbType]}
|
||||
language={editorLang} height="240px" onExecute={handleExecute}
|
||||
schema={(dbType === 'mysql' || dbType === 'postgres' || dbType === 'postgresql') ? editorSchema : undefined} />
|
||||
<div className="flex items-center justify-between px-3 py-1.5 bg-[var(--dg-bg-tertiary)] border-t border-[var(--dg-border)] text-[11px] text-[var(--dg-text-muted)]">
|
||||
<div className="flex items-center justify-between px-3 py-1.5 bg-[var(--dg-bg-tertiary)] border-t border-[var(--dg-border)] text-[11px] dg-text-muted">
|
||||
<span className="flex items-center gap-1.5">
|
||||
{dbTypeIcon[dbType] && <img src={dbTypeIcon[dbType]} alt="" className="w-3 h-3 object-contain" />}
|
||||
{dbTypeLabel[dbType]} 模式 · {sql.length} 字符
|
||||
@@ -428,32 +422,32 @@ export default function QueriesPage() {
|
||||
|
||||
{/* -- 查询结果 -- */}
|
||||
{result && (
|
||||
<Card className="dg-card overflow-hidden">
|
||||
<CardHeader className="border-b border-[var(--dg-border)] py-2.5">
|
||||
<div className="dg-card overflow-hidden">
|
||||
<div className="border-b border-[var(--dg-border)] py-2.5 px-4">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<CardTitle className="text-sm font-semibold flex items-center gap-2">
|
||||
<span className="text-sm font-semibold flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-400" />
|
||||
查询结果
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-3 text-xs text-[var(--dg-text-muted)]">
|
||||
</span>
|
||||
<div className="flex items-center gap-3 text-xs dg-text-secondary">
|
||||
<span className="flex items-center gap-1"><Clock className="w-3 h-3" />{result.execution_time_ms}ms</span>
|
||||
<span>{result.rows?.length || 0} 行</span>
|
||||
<span>{result.columns?.length || 0} 列</span>
|
||||
{result.rows && result.rows.length > 0 && (
|
||||
<button onClick={handleExportResult}
|
||||
className="flex items-center gap-1 px-2.5 py-1 text-xs font-medium bg-green-500/15 text-green-400 border border-green-500/30 rounded-md hover:bg-green-500/25 transition-all">
|
||||
className="dg-btn dg-btn-ghost dg-btn-sm">
|
||||
<Download className="w-3 h-3" />导出 CSV
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
</div>
|
||||
<div className="p-0">
|
||||
{result.columns && result.columns.length > 0 ? (
|
||||
<div ref={resultContainerRef} onScroll={handleResultScroll} className="overflow-auto max-h-[350px]">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="sticky top-0 z-10">
|
||||
<tr className="border-b border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)]">
|
||||
<tr className="dg-table-header">
|
||||
<th className="text-left px-3 py-2 font-semibold text-xs text-[var(--dg-text-muted)] w-10">#</th>
|
||||
{result.columns.map(col => (
|
||||
<th key={col} onClick={() => handleSort(col)}
|
||||
@@ -473,7 +467,7 @@ export default function QueriesPage() {
|
||||
{(sortedResult?.rows || []).slice(visibleRange.start, visibleRange.end).map((row, i) => {
|
||||
const ri = visibleRange.start + i;
|
||||
return (
|
||||
<tr key={ri} className="border-b border-[var(--dg-border-light)] hover:bg-[var(--dg-bg-hover)] transition-colors" style={{ height: rowHeight }}>
|
||||
<tr key={ri} className="dg-table-row" style={{ height: rowHeight }}>
|
||||
<td className="px-3 py-2 text-[10px] text-[var(--dg-text-muted)] font-mono">{ri + 1}</td>
|
||||
{result.columns.map(col => {
|
||||
const isSensitive = sensitiveKeywords.some(k => col.toLowerCase().includes(k));
|
||||
@@ -492,14 +486,14 @@ export default function QueriesPage() {
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-6 text-center text-[var(--dg-text-muted)] flex flex-col items-center gap-2">
|
||||
<div className="p-6 text-center dg-text-muted flex flex-col items-center gap-2">
|
||||
<Eye className="w-5 h-5" />
|
||||
<span className="text-sm">无结果数据</span>
|
||||
{result.affected_rows > 0 && <span className="text-xs">影响行数: {result.affected_rows}</span>}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* -- 安全提示 -- */}
|
||||
@@ -516,9 +510,9 @@ export default function QueriesPage() {
|
||||
<span className="text-sm font-semibold flex items-center gap-2">
|
||||
<History className="w-4 h-4 text-blue-400" />
|
||||
查询历史
|
||||
{history.length > 0 && <span className="text-xs text-[var(--dg-text-muted)] font-normal">({history.length})</span>}
|
||||
{history.length > 0 && <span className="text-xs dg-text-muted font-normal">({history.length})</span>}
|
||||
</span>
|
||||
<button onClick={() => setShowHistory(false)} className="text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)]">
|
||||
<button onClick={() => setShowHistory(false)} className="dg-btn dg-btn-ghost dg-btn-sm p-1">
|
||||
<PanelRightClose className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -527,22 +521,18 @@ export default function QueriesPage() {
|
||||
{history.length > 0 && (
|
||||
<div className="px-4 pb-2 space-y-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3 h-3 text-[var(--dg-text-muted)]" />
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3 h-3 dg-text-muted" />
|
||||
<input className="w-full pl-8 pr-3 py-1.5 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-md text-xs placeholder:text-[var(--dg-text-muted)] focus:border-blue-400 focus:outline-none"
|
||||
placeholder="搜索历史..." value={searchHistory} onChange={e => setSearchHistory(e.target.value)} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={() => setFavFilter(!favFilter)}
|
||||
className={`flex items-center gap-1 px-2 py-1 text-[11px] rounded-md transition-all ${
|
||||
favFilter ? 'bg-yellow-500/15 text-yellow-400 border border-yellow-500/30' : 'text-[var(--dg-text-muted)] hover:bg-[var(--dg-bg-hover)] border border-transparent'
|
||||
}`}>
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${favFilter ? 'bg-yellow-500/15 text-yellow-400 border-yellow-500/30' : ''}`}>
|
||||
<Star className="w-3 h-3" />仅收藏
|
||||
</button>
|
||||
<div className="flex-1" />
|
||||
<button onClick={handleClearHistory}
|
||||
className={`flex items-center gap-1 px-2 py-1 text-[11px] rounded-md transition-all ${
|
||||
clearConfirm ? 'text-red-400 bg-red-500/10' : 'text-[var(--dg-text-muted)] hover:text-red-400'
|
||||
}`}>
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${clearConfirm ? 'text-red-400 bg-red-500/10' : ''}`}>
|
||||
{clearConfirm ? <CheckCircle className="w-3 h-3" /> : <X className="w-3 h-3" />}
|
||||
{clearConfirm ? '确认清除' : '清除'}
|
||||
</button>
|
||||
@@ -553,29 +543,29 @@ export default function QueriesPage() {
|
||||
{/* 历史列表 */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{history.length === 0 ? (
|
||||
<div className="p-6 text-center text-xs text-[var(--dg-text-muted)]">暂无查询历史</div>
|
||||
<div className="p-6 text-center text-xs dg-text-muted">暂无查询历史</div>
|
||||
) : filteredHistory.length === 0 ? (
|
||||
<div className="p-6 text-center text-xs text-[var(--dg-text-muted)]">没有匹配的记录</div>
|
||||
<div className="p-6 text-center text-xs dg-text-muted">没有匹配的记录</div>
|
||||
) : (
|
||||
<div className="divide-y divide-[var(--dg-border-light)]">
|
||||
<div className="space-y-1.5 px-3 py-2">
|
||||
{filteredHistory.map(h => (
|
||||
<div key={h.id} className="px-4 py-2.5 hover:bg-[var(--dg-bg-hover)] transition-colors group">
|
||||
<div key={h.id} className={`dg-card px-3 py-2.5 group ${h.favorite ? 'bg-yellow-500/5 border-yellow-500/20' : ''}`}>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{h.db_type && dbTypeIcon[h.db_type] && <img src={dbTypeIcon[h.db_type]} alt="" className="w-3.5 h-3.5 object-contain" />}
|
||||
<span className={`text-[10px] px-1.5 py-0.5 rounded-full ${h.status === 'success' ? 'bg-green-500/15 text-green-400' : 'bg-red-500/15 text-red-400'}`}>
|
||||
{h.execution_time_ms}ms
|
||||
</span>
|
||||
<span className="text-[10px] text-[var(--dg-text-muted)]">{new Date(h.created_at).toLocaleTimeString()}</span>
|
||||
<span className="text-[10px] dg-text-muted">{new Date(h.created_at).toLocaleTimeString()}</span>
|
||||
<div className="flex-1" />
|
||||
<button onClick={() => toggleFavorite(h.id)}
|
||||
className={`p-0.5 rounded transition-colors ${h.favorite ? 'text-yellow-400' : 'text-[var(--dg-text-muted)] opacity-0 group-hover:opacity-100 hover:text-yellow-400'}`}>
|
||||
className={`p-0.5 rounded transition-colors ${h.favorite ? 'text-yellow-400' : 'dg-text-muted opacity-0 group-hover:opacity-100 hover:text-yellow-400'}`}>
|
||||
{h.favorite ? <Star className="w-3 h-3 fill-current" /> : <StarOff className="w-3 h-3" />}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<code className="flex-1 text-[11px] font-mono text-blue-400 truncate">{h.sql.substring(0, 80)}</code>
|
||||
<button onClick={() => setSql(h.sql)}
|
||||
className="p-1 rounded text-[var(--dg-text-muted)] hover:text-blue-400 transition-colors opacity-0 group-hover:opacity-100"
|
||||
className="dg-btn dg-btn-ghost dg-btn-sm p-1 opacity-0 group-hover:opacity-100"
|
||||
title="粘贴到编辑器">
|
||||
<ClipboardPaste className="w-3 h-3" />
|
||||
</button>
|
||||
|
||||
Reference in new issue
Block a user