feat(ui): 统一组件圆角样式,调整标题和按钮样式
This commit is contained in:
1 parent
9485366765
commit
822b2fad4c
16 files changed
+62
-60
No files matched your search
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>☁️</text></svg>" />
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>☁️</text></svg>" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>One API on Workers - 管理控制台</title>
|
<title>One API on Workers</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -44,17 +44,17 @@ export function BreakdownChartCard({
|
|||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
Array.from({ length: 4 }, (_, index) => (
|
Array.from({ length: 4 }, (_, index) => (
|
||||||
<div key={index} className="rounded-2xl border bg-muted/20 p-3 animate-pulse">
|
<div key={index} className="animate-pulse">
|
||||||
<div className="h-4 w-1/3 rounded bg-muted" />
|
<div className="h-4 w-1/3 bg-muted" />
|
||||||
<div className="mt-3 h-2 rounded-full bg-muted" />
|
<div className="mt-3 h-2 rounded-full bg-muted" />
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : isError ? (
|
) : isError ? (
|
||||||
<div className="rounded-2xl border border-dashed bg-muted/10 px-4 py-8 text-center text-sm text-muted-foreground">
|
<div className="px-4 py-8 text-center text-sm text-muted-foreground">
|
||||||
排行数据加载失败
|
排行数据加载失败
|
||||||
</div>
|
</div>
|
||||||
) : visibleItems.length === 0 ? (
|
) : visibleItems.length === 0 ? (
|
||||||
<div className="rounded-2xl border border-dashed bg-muted/10 px-4 py-8 text-center text-sm text-muted-foreground">
|
<div className="px-4 py-8 text-center text-sm text-muted-foreground">
|
||||||
当前时间范围暂无维度排行数据
|
当前时间范围暂无维度排行数据
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -94,9 +94,9 @@ export function BreakdownChartCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-2 h-2 bg-muted/60">
|
<div className="mt-2 h-0.5 bg-muted">
|
||||||
<div
|
<div
|
||||||
className={cn("h-2 bg-gradient-to-r from-primary to-primary/50", barClassName)}
|
className={cn("h-2 -translate-y-0.75 bg-gradient-to-r from-primary to-primary/50 -skew-x-12", barClassName)}
|
||||||
style={{ width: `${width}%` }}
|
style={{ width: `${width}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { AnalyticsRange, AnalyticsTrendPoint } from "@/types";
|
import { AnalyticsRange, AnalyticsTrendPoint } from "@/types";
|
||||||
import { formatCurrency } from "@/lib/utils";
|
import { formatCurrency } from "@/lib/utils";
|
||||||
|
import { Card } from "../ui/card";
|
||||||
|
|
||||||
const formatAxisLabel = (value: string, range: AnalyticsRange): string => {
|
const formatAxisLabel = (value: string, range: AnalyticsRange): string => {
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
@@ -10,7 +11,6 @@ const formatAxisLabel = (value: string, range: AnalyticsRange): string => {
|
|||||||
if (range === "24h") {
|
if (range === "24h") {
|
||||||
return date.toLocaleTimeString("zh-CN", {
|
return date.toLocaleTimeString("zh-CN", {
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,9 +28,6 @@ const formatCompactNumber = (value: number): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function TrendBarChart({ points, range }: { points: AnalyticsTrendPoint[]; range: AnalyticsRange }) {
|
export function TrendBarChart({ points, range }: { points: AnalyticsTrendPoint[]; range: AnalyticsRange }) {
|
||||||
const totalRequests = points.reduce((sum, point) => sum + point.requests, 0);
|
|
||||||
const totalFailures = points.reduce((sum, point) => sum + point.failures, 0);
|
|
||||||
const totalCost = points.reduce((sum, point) => sum + point.totalCost, 0);
|
|
||||||
const peakRequests = Math.max(...points.map((point) => point.requests), 1);
|
const peakRequests = Math.max(...points.map((point) => point.requests), 1);
|
||||||
const minColumnWidth = range === "90d" ? 28 : range === "30d" ? 34 : range === "24h" ? 42 : 56;
|
const minColumnWidth = range === "90d" ? 28 : range === "30d" ? 34 : range === "24h" ? 42 : 56;
|
||||||
const chartMinWidth = Math.max(points.length * minColumnWidth, 720);
|
const chartMinWidth = Math.max(points.length * minColumnWidth, 720);
|
||||||
@@ -45,7 +42,7 @@ export function TrendBarChart({ points, range }: { points: AnalyticsTrendPoint[]
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="overflow-x-auto rounded-2xl bg-card p-8 pb-6">
|
<Card className="overflow-x-auto border-0 p-8 pb-6">
|
||||||
<div className="w-full" style={{ minWidth: `${chartMinWidth}px` }}>
|
<div className="w-full" style={{ minWidth: `${chartMinWidth}px` }}>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="pointer-events-none absolute inset-x-0 top-0 h-56">
|
<div className="pointer-events-none absolute inset-x-0 top-0 h-56">
|
||||||
@@ -55,7 +52,7 @@ export function TrendBarChart({ points, range }: { points: AnalyticsTrendPoint[]
|
|||||||
className="absolute inset-x-0 flex items-center"
|
className="absolute inset-x-0 flex items-center"
|
||||||
style={{ top: tick.top }}
|
style={{ top: tick.top }}
|
||||||
>
|
>
|
||||||
<span className="w-10 -translate-y-1/2 pr-3 text-right text-[11px] text-muted-foreground">
|
<span className="w-10 -translate-y-1/2 pr-1 text-right text-[11px] text-muted-foreground">
|
||||||
{formatCompactNumber(Math.round(tick.value))}
|
{formatCompactNumber(Math.round(tick.value))}
|
||||||
</span>
|
</span>
|
||||||
{/* <div className="h-px flex-1 bg-border/20" /> */}
|
{/* <div className="h-px flex-1 bg-border/20" /> */}
|
||||||
@@ -64,7 +61,7 @@ export function TrendBarChart({ points, range }: { points: AnalyticsTrendPoint[]
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="ml-12 grid gap-1"
|
className="ml-10 grid gap-1"
|
||||||
style={{ gridTemplateColumns: `repeat(${points.length}, minmax(0, 1fr))` }}
|
style={{ gridTemplateColumns: `repeat(${points.length}, minmax(0, 1fr))` }}
|
||||||
>
|
>
|
||||||
{points.map((point) => {
|
{points.map((point) => {
|
||||||
@@ -73,24 +70,33 @@ export function TrendBarChart({ points, range }: { points: AnalyticsTrendPoint[]
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={`${point.timestamp}-${point.requests}`} className="min-w-0">
|
<div key={`${point.timestamp}-${point.requests}`} className="min-w-0">
|
||||||
<div className="flex h-56 items-end bg-muted/50">
|
|
||||||
<div
|
<div
|
||||||
className="w-full bg-gradient-to-t from-sky-500 to-cyan-400 transition-all duration-300"
|
className="group relative flex h-56 items-end bg-muted/50 outline-none hover:bg-muted"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={`${label},请求数 ${formatCompactNumber(point.requests)},花费 ${formatCurrency(point.totalCost)}`}
|
||||||
|
>
|
||||||
|
<div className="pointer-events-none absolute top-3 left-[-100%] right-[-100%] mx-auto z-20 w-24 rounded-md border border-border/50 bg-card px-3 py-2 text-left opacity-0 shadow-lg transition-all duration-200 translate-y-2 group-hover:translate-y-0 group-hover:opacity-100 group-focus-visible:translate-y-0 group-focus-visible:opacity-100">
|
||||||
|
<div className="text-[10px] text-muted-foreground">Requests</div>
|
||||||
|
<div className="text-xs font-semibold text-foreground">
|
||||||
|
{formatCompactNumber(point.requests)}
|
||||||
|
</div>
|
||||||
|
<div className="text-[10px] text-muted-foreground">Total Cost</div>
|
||||||
|
<div className="text-xs font-semibold text-foreground">{formatCurrency(point.totalCost)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute inset-0 ring-1 ring-sky-400/0 transition-all duration-300 group-focus-visible:ring-sky-400/30" />
|
||||||
|
<div
|
||||||
|
className="relative z-10 w-full bg-gradient-to-t from-sky-500 to-cyan-400 transition-all duration-300 group-hover:from-sky-400 group-hover:to-cyan-300 group-focus-visible:from-sky-400 group-focus-visible:to-cyan-300"
|
||||||
style={{ height: `${barHeight}%` }}
|
style={{ height: `${barHeight}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-center text-[11px] text-muted-foreground">{label}</div>
|
<div className="mt-1 text-center text-[11px] text-muted-foreground">{label}</div>
|
||||||
<div className="text-center text-sm font-medium">{formatCompactNumber(point.requests)}</div>
|
|
||||||
<div className="text-center text-[11px] text-muted-foreground">
|
|
||||||
{formatCurrency(point.totalCost)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ export function Sidebar({
|
|||||||
onClick={() => onNavigate?.()}
|
onClick={() => onNavigate?.()}
|
||||||
title={collapsed ? item.title : undefined}
|
title={collapsed ? item.title : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative flex items-center gap-3 px-3 py-2 rounded-lg text-[13px] font-medium transition-all duration-150",
|
"group relative flex items-center gap-3 px-3 py-2 rounded-md text-[13px] font-medium transition-all duration-150",
|
||||||
collapsed && "justify-center px-0 w-10 h-10 mx-auto",
|
collapsed && "justify-center px-0 w-10 h-10 mx-auto",
|
||||||
isActive
|
isActive
|
||||||
? "bg-primary text-primary-foreground shadow-sm shadow-primary/20"
|
? "bg-primary text-primary-foreground shadow-sm shadow-primary/20"
|
||||||
@@ -195,7 +195,9 @@ export function Sidebar({
|
|||||||
onClick={onToggleCollapse}
|
onClick={onToggleCollapse}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center rounded-md transition-colors duration-150",
|
"flex items-center justify-center rounded-md transition-colors duration-150",
|
||||||
collapsed ? "w-10 h-9 hover:bg-muted text-muted-foreground hover:text-foreground" : "flex-1 h-8 gap-1.5 px-2.5 hover:bg-muted/70 text-muted-foreground hover:text-foreground text-[11px] font-medium",
|
collapsed
|
||||||
|
? "w-10 h-9 hover:bg-muted text-muted-foreground hover:text-foreground"
|
||||||
|
: "flex-1 h-8 gap-1.5 px-2.5 hover:bg-muted/70 text-muted-foreground hover:text-foreground text-[11px] font-medium",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{collapsed ? (
|
{collapsed ? (
|
||||||
@@ -216,7 +218,7 @@ export function Sidebar({
|
|||||||
type="button"
|
type="button"
|
||||||
title={collapsed ? "退出登录" : undefined}
|
title={collapsed ? "退出登录" : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center gap-2 w-full rounded-lg text-[13px] font-medium transition-all duration-150",
|
"flex items-center justify-center gap-2 w-full rounded-md text-[13px] font-medium transition-all duration-150",
|
||||||
collapsed ? "h-9" : "h-9 px-3",
|
collapsed ? "h-9" : "h-9 px-3",
|
||||||
"bg-destructive/8 text-destructive hover:bg-destructive/15",
|
"bg-destructive/8 text-destructive hover:bg-destructive/15",
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const Alert = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
role="alert"
|
role="alert"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative w-full rounded-lg border p-4 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&:has(svg)]:pl-11",
|
"relative w-full rounded-md border p-4 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&:has(svg)]:pl-11",
|
||||||
{
|
{
|
||||||
"bg-background text-foreground": variant === "default",
|
"bg-background text-foreground": variant === "default",
|
||||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive":
|
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive":
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export const AutoCompleteInput = React.forwardRef<HTMLInputElement, AutoComplete
|
|||||||
{isOpen && !disabled && (
|
{isOpen && !disabled && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute z-20 mt-1 w-full overflow-hidden rounded-lg border bg-popover shadow-lg',
|
'absolute z-20 mt-1 w-full overflow-hidden rounded-md border bg-popover shadow-lg',
|
||||||
dropdownClassName
|
dropdownClassName
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:shadow-md disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer active:scale-[0.97] [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:shadow-md disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer active:scale-[0.97] [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -21,8 +21,8 @@ const buttonVariants = cva(
|
|||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-4 py-2",
|
default: "h-10 px-4 py-2",
|
||||||
sm: "h-9 rounded-lg px-3",
|
sm: "h-9 rounded-md px-3",
|
||||||
lg: "h-11 rounded-lg px-8",
|
lg: "h-11 rounded-md px-8",
|
||||||
icon: "h-10 w-10",
|
icon: "h-10 w-10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const Card = React.forwardRef<
|
|||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-xl border bg-card text-card-foreground shadow-soft transition-shadow duration-200",
|
"rounded-md border bg-card text-card-foreground shadow-soft transition-shadow duration-200",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-xl",
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:opacity-100 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-md opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:opacity-100 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-10 w-full rounded-lg border border-input px-3 py-2 text-sm shadow-sm transition-all duration-200 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:border-primary/50 hover:border-muted-foreground/30 disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-10 w-full rounded-md border border-input px-3 py-2 text-sm shadow-sm transition-all duration-200 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:border-primary/50 hover:border-muted-foreground/30 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ interface SelectOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TRIGGER_BASE_CLASS =
|
const TRIGGER_BASE_CLASS =
|
||||||
'group relative flex h-10 w-full items-center rounded-lg border border-input bg-transparent px-3 py-2 pr-10 text-sm text-foreground shadow-sm outline-none transition-all duration-200 hover:border-muted-foreground/30 focus-visible:border-primary/50 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50'
|
'group relative flex h-10 w-full items-center rounded-md border border-input bg-transparent px-3 py-2 pr-10 text-sm text-foreground shadow-sm outline-none transition-all duration-200 hover:border-muted-foreground/30 focus-visible:border-primary/50 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50'
|
||||||
|
|
||||||
const CONTENT_BASE_CLASS =
|
const CONTENT_BASE_CLASS =
|
||||||
'absolute z-50 mt-1 w-full overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-lg'
|
'absolute z-50 mt-1 w-full overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-lg'
|
||||||
|
|
||||||
const ITEM_BASE_CLASS =
|
const ITEM_BASE_CLASS =
|
||||||
'relative flex w-full select-none items-center px-3 py-2 pr-9 text-left text-sm text-foreground outline-none transition-colors duration-150 hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-45'
|
'relative flex w-full select-none items-center px-3 py-2 pr-9 text-left text-sm text-foreground outline-none transition-colors duration-150 hover:bg-muted/80 disabled:cursor-not-allowed disabled:opacity-45'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const Textarea = React.forwardRef<
|
|||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-[80px] w-full rounded-lg border border-input px-3 py-2 text-sm shadow-sm transition-all duration-200 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:border-primary/50 hover:border-muted-foreground/30 disabled:cursor-not-allowed disabled:opacity-50",
|
"flex min-h-[80px] w-full rounded-md border border-input px-3 py-2 text-sm shadow-sm transition-all duration-200 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:border-primary/50 hover:border-muted-foreground/30 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
--color-popover-foreground: hsl(224 71% 4%);
|
--color-popover-foreground: hsl(224 71% 4%);
|
||||||
|
|
||||||
/* Primary - Vibrant indigo */
|
/* Primary - Vibrant indigo */
|
||||||
--color-primary: hsl(234 89% 60%);
|
--color-primary: #242424;
|
||||||
--color-primary-foreground: hsl(0 0% 100%);
|
--color-primary-foreground: hsl(0 0% 100%);
|
||||||
|
|
||||||
/* Secondary */
|
/* Secondary */
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ const BREAKDOWN_CHARTS: Array<{
|
|||||||
badgeClassName: string;
|
badgeClassName: string;
|
||||||
}> = [
|
}> = [
|
||||||
{
|
{
|
||||||
dimension: "token",
|
dimension: "model",
|
||||||
title: "令牌排行",
|
title: "模型排行",
|
||||||
description: "按成本优先排序,识别最活跃的访问凭证。",
|
description: "看清哪些模型是主要成本与流量入口。",
|
||||||
barClassName: "from-sky-500 to-cyan-400",
|
barClassName: "from-amber-500 to-orange-400",
|
||||||
badgeClassName: "bg-sky-500/12 text-sky-600 dark:text-sky-400",
|
badgeClassName: "bg-amber-500/12 text-amber-700 dark:text-amber-400",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dimension: "channel",
|
dimension: "channel",
|
||||||
@@ -46,11 +46,11 @@ const BREAKDOWN_CHARTS: Array<{
|
|||||||
badgeClassName: "bg-emerald-500/12 text-emerald-600 dark:text-emerald-400",
|
badgeClassName: "bg-emerald-500/12 text-emerald-600 dark:text-emerald-400",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dimension: "model",
|
dimension: "token",
|
||||||
title: "模型排行",
|
title: "令牌排行",
|
||||||
description: "看清哪些模型是主要成本与流量入口。",
|
description: "按成本优先排序,识别最活跃的访问凭证。",
|
||||||
barClassName: "from-amber-500 to-orange-400",
|
barClassName: "from-sky-500 to-cyan-400",
|
||||||
badgeClassName: "bg-amber-500/12 text-amber-700 dark:text-amber-400",
|
badgeClassName: "bg-sky-500/12 text-sky-600 dark:text-sky-400",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dimension: "provider",
|
dimension: "provider",
|
||||||
@@ -77,15 +77,6 @@ const formatDuration = (value: number): string => {
|
|||||||
return `${Math.round(value)} ms`;
|
return `${Math.round(value)} ms`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmptyState = ({ title, description }: { title: string; description: string }) => {
|
|
||||||
return (
|
|
||||||
<div className="rounded-xl border border-dashed bg-muted/20 px-4 py-10 text-center">
|
|
||||||
<p className="text-sm font-medium">{title}</p>
|
|
||||||
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Analytics() {
|
export function Analytics() {
|
||||||
const [range, setRange] = useState<AnalyticsRange>("7d");
|
const [range, setRange] = useState<AnalyticsRange>("7d");
|
||||||
|
|
||||||
@@ -253,7 +244,10 @@ export function Analytics() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!trend || trend.points.length === 0 ? (
|
{!trend || trend.points.length === 0 ? (
|
||||||
<EmptyState title="暂无趋势数据" description="写入第一批使用事件后,这里会展示完整柱状趋势。" />
|
<Card className="border-0 flex flex-col items-center justify-center py-16">
|
||||||
|
<p className="text-sm font-medium">暂无趋势数据</p>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">写入第一批使用事件后,这里会展示完整柱状趋势。</p>
|
||||||
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<TrendBarChart points={trend.points} range={range} />
|
<TrendBarChart points={trend.points} range={range} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export function Dashboard() {
|
|||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
onClick={openAuthModal}
|
onClick={openAuthModal}
|
||||||
className="h-12 px-6 text-[15px] rounded-xl shadow-lg shadow-primary/20"
|
className="h-12 px-6 text-[15px] shadow-lg shadow-primary/20"
|
||||||
>
|
>
|
||||||
管理员登录
|
管理员登录
|
||||||
<ArrowRight className="h-4 w-4 ml-1" />
|
<ArrowRight className="h-4 w-4 ml-1" />
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@
|
|||||||
{
|
{
|
||||||
"binding": "DB",
|
"binding": "DB",
|
||||||
"database_name": "one-api-workers",
|
"database_name": "one-api-workers",
|
||||||
"database_id": ""
|
"database_id": "5d429962-32e6-4895-8d40-467237292800"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"analytics_engine_datasets": [
|
"analytics_engine_datasets": [
|
||||||
|
|||||||
Reference in new issue
Block a user