diff --git a/frontend/index.html b/frontend/index.html index 87d105e..418a47a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,6 +5,35 @@ OpsxSQL +
diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index 37a7d4b..d6f3dfa 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -5,19 +5,19 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: - "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20:ring-destructive/40", outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground:bg-input/50", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + "hover:bg-accent hover:text-accent-foreground:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { diff --git a/frontend/src/index.css b/frontend/src/index.css index f495717..1a2558b 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -3,89 +3,10 @@ @tailwind utilities; @layer base { - /* ===== Theme 1: Ocean Deep (深蓝专业) — Default ===== */ + /* ===== Design Tokens(--dg-* 单一事实源;多主题经 data-theme 整组覆盖) ===== + tailwind 语义色(border/background/card/primary/...)由 tailwind.config.js 桥接到这些变量 */ :root { - --background: 220 13% 5%; - --foreground: 210 40% 98%; - --card: 220 13% 7%; - --card-foreground: 210 40% 98%; - --popover: 220 13% 7%; - --popover-foreground: 210 40% 98%; - --primary: 217 91% 60%; - --primary-foreground: 0 0% 100%; - --secondary: 220 13% 14%; - --secondary-foreground: 210 40% 98%; - --muted: 220 13% 14%; - --muted-foreground: 215 20% 55%; - --accent: 220 13% 14%; - --accent-foreground: 210 40% 98%; - --destructive: 0 62% 50%; - --destructive-foreground: 0 0% 100%; - --border: 220 13% 18%; - --input: 220 13% 14%; - --ring: 217 91% 60%; - --radius: 0.625rem; - --sidebar-background: 220 13% 7%; - --sidebar-foreground: 210 40% 90%; - --sidebar-primary: 217 91% 60%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 220 13% 14%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 220 13% 18%; - --sidebar-ring: 217 91% 60%; - --dg-bg-primary: #0d1117; - --dg-bg-secondary: #161b22; - --dg-bg-tertiary: #21262d; - --dg-bg-hover: #30363d; - --dg-border: #30363d; - --dg-border-light: #21262d; - --dg-text-primary: #f0f6fc; - --dg-text-secondary: #8b949e; - --dg-text-muted: #6e7681; - --dg-blue: #58a6ff; - --dg-green: #3fb950; - --dg-red: #f85149; - --dg-orange: #f0883e; - --dg-purple: #7c8aa5; - --dg-cyan: #39c5cf; - --dg-yellow: #d29922; - --dg-card-hover-border: rgba(88, 166, 255, 0.5); - --dg-gradient-start: #2563eb; - --dg-gradient-end: #0ea5e9; - --dg-gradient-text-start: #60a5fa; - --dg-gradient-text-end: #93c5fd; - --dg-input-focus-ring: rgba(96, 165, 250, 0.2); - } - - [data-theme="ocean-deep"] { - --background: 220 13% 5%; - --foreground: 210 40% 98%; - --card: 220 13% 7%; - --card-foreground: 210 40% 98%; - --popover: 220 13% 7%; - --popover-foreground: 210 40% 98%; - --primary: 217 91% 60%; - --primary-foreground: 0 0% 100%; - --secondary: 220 13% 14%; - --secondary-foreground: 210 40% 98%; - --muted: 220 13% 14%; - --muted-foreground: 215 20% 55%; - --accent: 220 13% 14%; - --accent-foreground: 210 40% 98%; - --destructive: 0 62% 50%; - --destructive-foreground: 0 0% 100%; - --border: 220 13% 18%; - --input: 220 13% 14%; - --ring: 217 91% 60%; - --radius: 0.625rem; - --sidebar-background: 220 13% 7%; - --sidebar-foreground: 210 40% 90%; - --sidebar-primary: 217 91% 60%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 220 13% 14%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 220 13% 18%; - --sidebar-ring: 217 91% 60%; + --radius: 0.5rem; /* 卡片/按钮/输入框圆角主值 */ --dg-bg-primary: #0d1117; --dg-bg-secondary: #161b22; --dg-bg-tertiary: #21262d; @@ -113,34 +34,6 @@ /* ===== Theme 2: Light Professional (浅色商务) ===== */ [data-theme="light-professional"], [data-theme="light"] { - --background: 220 14% 97%; - --foreground: 220 14% 10%; - --card: 0 0% 100%; - --card-foreground: 220 14% 10%; - --popover: 0 0% 100%; - --popover-foreground: 220 14% 10%; - --primary: 217 91% 60%; - --primary-foreground: 0 0% 100%; - --secondary: 220 14% 96%; - --secondary-foreground: 220 14% 10%; - --muted: 220 14% 96%; - --muted-foreground: 220 10% 46%; - --accent: 220 14% 96%; - --accent-foreground: 220 14% 10%; - --destructive: 0 72% 51%; - --destructive-foreground: 0 0% 100%; - --border: 220 13% 91%; - --input: 220 13% 91%; - --ring: 217 91% 60%; - --radius: 0.625rem; - --sidebar-background: 0 0% 100%; - --sidebar-foreground: 220 14% 20%; - --sidebar-primary: 217 91% 60%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 220 14% 94%; - --sidebar-accent-foreground: 220 14% 10%; - --sidebar-border: 220 13% 91%; - --sidebar-ring: 217 91% 60%; --dg-bg-primary: #f5f6f8; --dg-bg-secondary: #ffffff; --dg-bg-tertiary: #eef1f5; @@ -167,34 +60,6 @@ /* ===== Theme 4: Pine Green (松绿自然) ===== */ [data-theme="pine-green"] { - --background: 150 25% 5%; - --foreground: 140 60% 97%; - --card: 150 25% 9%; - --card-foreground: 140 60% 97%; - --popover: 150 25% 9%; - --popover-foreground: 140 60% 97%; - --primary: 142 71% 45%; - --primary-foreground: 150 25% 5%; - --secondary: 150 20% 18%; - --secondary-foreground: 140 60% 97%; - --muted: 150 20% 18%; - --muted-foreground: 150 20% 55%; - --accent: 150 20% 18%; - --accent-foreground: 140 60% 97%; - --destructive: 0 90% 72%; - --destructive-foreground: 150 25% 5%; - --border: 150 20% 18%; - --input: 150 20% 18%; - --ring: 142 71% 45%; - --radius: 0.625rem; - --sidebar-background: 150 25% 7%; - --sidebar-foreground: 140 40% 88%; - --sidebar-primary: 142 71% 45%; - --sidebar-primary-foreground: 150 25% 5%; - --sidebar-accent: 150 20% 18%; - --sidebar-accent-foreground: 140 60% 97%; - --sidebar-border: 150 20% 18%; - --sidebar-ring: 142 71% 45%; --dg-bg-primary: #0a1210; --dg-bg-secondary: #111f1a; --dg-bg-tertiary: #192c24; @@ -289,14 +154,6 @@ .scrollbar-thin::-webkit-scrollbar-thumb:hover { background: var(--dg-text-muted); } - [data-theme="light-professional"] .scrollbar-thin::-webkit-scrollbar-thumb, - [data-theme="light"] .scrollbar-thin::-webkit-scrollbar-thumb { - background: #d1d5db; - } - [data-theme="light-professional"] .scrollbar-thin::-webkit-scrollbar-thumb:hover, - [data-theme="light"] .scrollbar-thin::-webkit-scrollbar-thumb:hover { - background: #9ca3af; - } /* ===== Design System Utilities ===== */ diff --git a/frontend/src/lib/constants.ts b/frontend/src/lib/constants.ts index b78e1b2..fc8e826 100644 --- a/frontend/src/lib/constants.ts +++ b/frontend/src/lib/constants.ts @@ -65,9 +65,29 @@ export const statusColorMap: Record = { rollback: 'bg-orange-500/15 text-orange-400', }; -export const dbTypeColorMap: Record = { +/** + * 数据库品牌色(刻意不随主题切换,是产品语义而非主题语义) + * 全站唯一源:徽章淡色版 dbTypeColorMap / 实色版 dbTypeBrandColorMap + */ +const DB_TYPE_BADGE = { mysql: 'bg-[rgba(0,117,143,0.15)] text-[#26a69a]', postgres: 'bg-[rgba(51,103,145,0.15)] text-[#42a5f5]', redis: 'bg-[rgba(220,50,32,0.15)] text-[#ef5350]', mongo: 'bg-[rgba(71,162,72,0.15)] text-[#66bb6a]', }; + +export const dbTypeColorMap: Record = { + ...DB_TYPE_BADGE, + postgresql: DB_TYPE_BADGE.postgres, + pg: DB_TYPE_BADGE.postgres, + mongodb: DB_TYPE_BADGE.mongo, +}; + +export const dbTypeBrandColorMap: Record = { + mysql: 'bg-[#00758f]', + postgres: 'bg-[#336791]', + postgresql: 'bg-[#336791]', + redis: 'bg-[#dc382d]', + mongo: 'bg-[#47a248]', + mongodb: 'bg-[#47a248]', +}; diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 21eb5b9..0be0906 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -7,7 +7,7 @@ import { FileText, ArrowUpRight, ArrowDownRight, } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { statusMap, statusColorMap, dbTypeColorMap } from '@/lib/constants'; +import { statusMap, statusColorMap, dbTypeColorMap, dbTypeBrandColorMap } from '@/lib/constants'; import { useTheme } from '@/hooks/useTheme'; import { useAuth } from '@/hooks/useAuth'; import { dashboardApi } from '@/api/dashboard'; @@ -378,10 +378,7 @@ export default function Dashboard() { return (
@@ -529,10 +526,10 @@ export default function Dashboard() {
查询类型分布
{(queryStats?.type_distribution ?? [ - { label: 'MySQL', count: 0, color: 'bg-[#00758f]' }, - { label: 'PostgreSQL', count: 0, color: 'bg-[#336791]' }, - { label: 'Redis', count: 0, color: 'bg-[#dc382d]' }, - { label: 'MongoDB', count: 0, color: 'bg-[#47a248]' }, + { label: 'MySQL', count: 0, color: dbTypeBrandColorMap.mysql }, + { label: 'PostgreSQL', count: 0, color: dbTypeBrandColorMap.postgres }, + { label: 'Redis', count: 0, color: dbTypeBrandColorMap.redis }, + { label: 'MongoDB', count: 0, color: dbTypeBrandColorMap.mongodb }, ]).map((item: any) => { const total = queryStats?.total || 1; return ( diff --git a/frontend/src/pages/InstancePage.tsx b/frontend/src/pages/InstancePage.tsx index 8504049..a4d5d3b 100644 --- a/frontend/src/pages/InstancePage.tsx +++ b/frontend/src/pages/InstancePage.tsx @@ -9,6 +9,7 @@ import { } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { useActionPermission } from '@/hooks/useAuth'; +import { dbTypeColorMap } from '@/lib/constants'; import PageHeader from '@/components/PageHeader'; import EmptyState from '@/components/EmptyState'; import { instanceApi } from '@/api/instance'; @@ -51,13 +52,13 @@ type ViewMode = 'grid' | 'list'; /* ------------------------------------------------------------------ */ /* DB Brand Icons */ -const dbTypeConfig: Record = { - mysql: { icon: MySQLIcon, color: 'text-[#26a69a]', bg: 'bg-[rgba(0,117,143,0.15)]', label: 'MySQL' }, - postgres: { icon: PostgreSQLIcon, color: 'text-[#42a5f5]', bg: 'bg-[rgba(51,103,145,0.15)]', label: 'PostgreSQL' }, - postgresql: { icon: PostgreSQLIcon, color: 'text-[#42a5f5]', bg: 'bg-[rgba(51,103,145,0.15)]', label: 'PostgreSQL' }, - pg: { icon: PostgreSQLIcon, color: 'text-[#42a5f5]', bg: 'bg-[rgba(51,103,145,0.15)]', label: 'PostgreSQL' }, - redis: { icon: RedisIcon, color: 'text-[#ef5350]', bg: 'bg-[rgba(220,50,32,0.15)]', label: 'Redis' }, - mongo: { icon: MongoDBIcon, color: 'text-[#66bb6a]', bg: 'bg-[rgba(71,162,72,0.15)]', label: 'MongoDB' }, +const dbTypeConfig: Record = { + mysql: { icon: MySQLIcon, badge: dbTypeColorMap.mysql, label: 'MySQL' }, + postgres: { icon: PostgreSQLIcon, badge: dbTypeColorMap.postgres, label: 'PostgreSQL' }, + postgresql: { icon: PostgreSQLIcon, badge: dbTypeColorMap.postgresql, label: 'PostgreSQL' }, + pg: { icon: PostgreSQLIcon, badge: dbTypeColorMap.pg, label: 'PostgreSQL' }, + redis: { icon: RedisIcon, badge: dbTypeColorMap.redis, label: 'Redis' }, + mongo: { icon: MongoDBIcon, badge: dbTypeColorMap.mongo, label: 'MongoDB' }, }; const envLabel: Record = { @@ -541,7 +542,7 @@ export default function InstancePage() {
-
+
@@ -689,7 +690,7 @@ export default function InstancePage() { {instance.name} - + {ic.label} @@ -923,7 +924,7 @@ function QuickImportDialog({ onClose, onImported }: { onClose: () => void; onImp /* ------------------------------------------------------------------ */ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete, onEdit, onClone }: { instance: InstanceItem; onBack: () => void; - config: { icon: React.ElementType; color: string; bg: string }; + config: { icon: React.ElementType; badge: string }; canEdit: boolean; canDelete: boolean; onDelete: () => void; onEdit: () => void; onClone?: () => void; }) { const [testing, setTesting] = useState(false); @@ -965,8 +966,8 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete
-
- +
+

@@ -1045,7 +1046,7 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete = 90 ? '#4ade80' : health >= 70 ? '#facc15' : health >= 50 ? '#fb923c' : '#f87171'} + stroke={health >= 90 ? 'var(--dg-green)' : health >= 70 ? 'var(--dg-yellow)' : health >= 50 ? 'var(--dg-orange)' : 'var(--dg-red)'} strokeWidth="8" strokeLinecap="round" strokeDasharray={`${(health / 100) * 264} 264`} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index cb8a316..2e45a07 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,55 +1,46 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - darkMode: ["class"], + // 多主题经 data-theme 属性切换(--dg-* 单源),无 class 暗色模式 content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], theme: { extend: { colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", + // 语义色全部派生自 --dg-* 设计 Token(唯一手写源) + border: "var(--dg-border)", + input: "var(--dg-border)", + ring: "var(--dg-blue)", + background: "var(--dg-bg-primary)", + foreground: "var(--dg-text-primary)", primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", + DEFAULT: "var(--dg-blue)", + foreground: "#ffffff", }, secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", + DEFAULT: "var(--dg-bg-tertiary)", + foreground: "var(--dg-text-primary)", }, destructive: { - DEFAULT: "hsl(var(--destructive) / )", - foreground: "hsl(var(--destructive-foreground) / )", + DEFAULT: "var(--dg-red)", + foreground: "#ffffff", }, muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", + DEFAULT: "var(--dg-bg-tertiary)", + foreground: "var(--dg-text-muted)", }, accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", + DEFAULT: "var(--dg-bg-hover)", + foreground: "var(--dg-text-primary)", }, popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", + DEFAULT: "var(--dg-bg-secondary)", + foreground: "var(--dg-text-primary)", }, card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - sidebar: { - DEFAULT: "hsl(var(--sidebar-background))", - foreground: "hsl(var(--sidebar-foreground))", - primary: "hsl(var(--sidebar-primary))", - "primary-foreground": "hsl(var(--sidebar-primary-foreground))", - accent: "hsl(var(--sidebar-accent))", - "accent-foreground": "hsl(var(--sidebar-accent-foreground))", - border: "hsl(var(--sidebar-border))", - ring: "hsl(var(--sidebar-ring))", + DEFAULT: "var(--dg-bg-secondary)", + foreground: "var(--dg-text-primary)", }, }, - borderRadius: { +borderRadius: { xl: "calc(var(--radius) + 4px)", lg: "var(--radius)", md: "calc(var(--radius) - 2px)", @@ -59,25 +50,6 @@ module.exports = { boxShadow: { xs: "0 1px 2px 0 rgb(0 0 0 / 0.05)", }, - keyframes: { - "accordion-down": { - from: { height: "0" }, - to: { height: "var(--radix-accordion-content-height)" }, - }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: "0" }, - }, - "caret-blink": { - "0%,70%,100%": { opacity: "1" }, - "20%,50%": { opacity: "0" }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - "caret-blink": "caret-blink 1.25s ease-out infinite", - }, }, }, plugins: [require("tailwindcss-animate")],