refactor(frontend): unify theme tokens to single --dg-* source
- remove shadcn HSL dual-track vars (523->416->~300 lines index.css); tailwind semantic colors (border/background/card/primary/destructive/ popover/muted/accent) now bridge directly to var(--dg-*) tokens - drop dead darkMode:['class'] config and inert dark: variants in kept ui components; theme switching is data-theme based (4 themes intact) - first-frame inline theme script in index.html (reads namespaced key with legacy fallback, resolves system, sets color-scheme) - no flash - consolidate duplicated db-type brand colors into lib/constants.ts single source (dbTypeColorMap + dbTypeBrandColorMap, with pg/mongodb aliases); instance health stroke now uses theme tokens - verified via browser: dark #0d1117 / light #f5f6f8 both resolve, tokens cascade correctly
This commit is contained in:
1 parent
a213d08e31
commit
ac563d1c5e
7 files changed
+99
-223
No files matched your search
@@ -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: {
|
||||
|
||||
+3
-146
@@ -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 ===== */
|
||||
|
||||
|
||||
@@ -65,9 +65,29 @@ export const statusColorMap: Record<string, string> = {
|
||||
rollback: 'bg-orange-500/15 text-orange-400',
|
||||
};
|
||||
|
||||
export const dbTypeColorMap: Record<string, string> = {
|
||||
/**
|
||||
* 数据库品牌色(刻意不随主题切换,是产品语义而非主题语义)
|
||||
* 全站唯一源:徽章淡色版 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<string, string> = {
|
||||
...DB_TYPE_BADGE,
|
||||
postgresql: DB_TYPE_BADGE.postgres,
|
||||
pg: DB_TYPE_BADGE.postgres,
|
||||
mongodb: DB_TYPE_BADGE.mongo,
|
||||
};
|
||||
|
||||
export const dbTypeBrandColorMap: Record<string, string> = {
|
||||
mysql: 'bg-[#00758f]',
|
||||
postgres: 'bg-[#336791]',
|
||||
postgresql: 'bg-[#336791]',
|
||||
redis: 'bg-[#dc382d]',
|
||||
mongo: 'bg-[#47a248]',
|
||||
mongodb: 'bg-[#47a248]',
|
||||
};
|
||||
@@ -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 (
|
||||
<div key={inst.id} className="flex items-center px-5 py-3 border-b border-[var(--dg-border-light)] last:border-0 hover:bg-[var(--dg-bg-hover)] transition-colors cursor-pointer group">
|
||||
<div className={`w-9 h-9 rounded-lg flex items-center justify-center mr-3.5 flex-shrink-0 ${
|
||||
inst.dbType === 'mysql' ? 'bg-[rgba(0,117,143,0.2)] text-[#26a69a]' :
|
||||
inst.dbType === 'redis' ? 'bg-[rgba(220,50,32,0.2)] text-[#ef5350]' :
|
||||
inst.dbType === 'postgres' ? 'bg-[rgba(51,103,145,0.2)] text-[#42a5f5]' :
|
||||
'bg-[rgba(71,162,72,0.2)] text-[#66bb6a]'
|
||||
dbTypeColorMap[inst.dbType] || dbTypeColorMap.mysql
|
||||
}`}>
|
||||
<Database className="w-4.5 h-4.5" />
|
||||
</div>
|
||||
@@ -529,10 +526,10 @@ export default function Dashboard() {
|
||||
<div className="space-y-2.5">
|
||||
<div className="text-xs text-[var(--dg-text-muted)] font-medium">查询类型分布</div>
|
||||
{(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 (
|
||||
|
||||
@@ -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<string, { icon: React.ElementType; color: string; bg: string; label: string }> = {
|
||||
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<string, { icon: React.ElementType; badge: string; label: string }> = {
|
||||
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<string, string> = {
|
||||
@@ -541,7 +542,7 @@ export default function InstancePage() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-start justify-between mb-4 pl-6">
|
||||
<div className={`w-9 h-9 rounded-lg ${ic.bg} flex items-center justify-center`}>
|
||||
<div className={`w-9 h-9 rounded-lg ${ic.badge} flex items-center justify-center`}>
|
||||
<IconComp className="w-5 h-5 object-contain" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -689,7 +690,7 @@ export default function InstancePage() {
|
||||
</td>
|
||||
<td className="px-4 py-3 font-medium">{instance.name}</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className={`flex items-center gap-1.5 ${ic.color}`}>
|
||||
<span className={`flex items-center gap-1.5 ${ic.badge} px-1.5 rounded-md`}>
|
||||
<IconComp className="w-4 h-4 object-contain" />
|
||||
<span className="text-xs">{ic.label}</span>
|
||||
</span>
|
||||
@@ -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
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
</button>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${config.bg} flex items-center justify-center`}>
|
||||
<DetailIcon className={`w-5 h-5 ${config.color}`} />
|
||||
<div className={`w-10 h-10 rounded-xl ${config.badge} flex items-center justify-center`}>
|
||||
<DetailIcon className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold flex items-center gap-3">
|
||||
@@ -1045,7 +1046,7 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete
|
||||
<circle cx="50" cy="50" r="42" fill="none" stroke="var(--dg-bg-tertiary)" strokeWidth="8" />
|
||||
<circle
|
||||
cx="50" cy="50" r="42" fill="none"
|
||||
stroke={health >= 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`}
|
||||
|
||||
Reference in new issue
Block a user