@teamix-evo/skills 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/manifest.json +23 -37
- package/package.json +2 -2
- package/src/teamix-evo-design-opentrek/SKILL.md +210 -42
- package/src/teamix-evo-design-opentrek/boundaries.md +3 -3
- package/src/teamix-evo-design-opentrek/components.md +41 -40
- package/src/teamix-evo-design-opentrek/examples/detail-ai-gateway-1.html +1069 -0
- package/src/teamix-evo-design-opentrek/examples/detail-ai-gateway-instance.html +941 -0
- package/src/teamix-evo-design-opentrek/examples/detail-page-api-doc.html +906 -0
- package/src/teamix-evo-design-opentrek/examples/detail-page-config.html +993 -0
- package/src/teamix-evo-design-opentrek/examples/detail-page-monitor.html +1339 -0
- package/src/teamix-evo-design-opentrek/examples/detail-page.html +933 -0
- package/src/teamix-evo-design-opentrek/examples/settings-page.html +1119 -0
- package/src/teamix-evo-design-opentrek/examples/standard-card-list.html +1094 -0
- package/src/teamix-evo-design-opentrek/examples/standard-table-list.html +1361 -0
- package/src/teamix-evo-design-opentrek/examples/wizard-form-page.html +877 -0
- package/src/teamix-evo-design-opentrek/flows.md +85 -12
- package/src/teamix-evo-design-opentrek/foundations.md +9 -9
- package/src/teamix-evo-design-opentrek/generation-flow.md +15 -3
- package/src/teamix-evo-design-opentrek/pages/detail-page/SKILL.md +260 -0
- package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/api-doc-detail.md +163 -0
- package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/comparison-detail.md +100 -0
- package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/monitor-detail.md +190 -0
- package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/resource-detail.md +148 -0
- package/src/teamix-evo-design-opentrek/pages/form-page/SKILL.md +362 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/SKILL.md +286 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/_shared/action-column-spec.md +60 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/_shared/column-meta-rules.md +117 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/_shared/search-combo-spec.md +194 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/_shared/state-action-pattern.md +51 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/advanced-filter-list.md +94 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/card-list.md +558 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/drawer-list.md +76 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/expandable-list.md +70 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/l2-sidebar-list.md +73 -0
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/standard-list.md +198 -0
- package/src/teamix-evo-design-opentrek/patterns/color-mapping.md +1 -1
- package/src/teamix-evo-design-opentrek/patterns/detail-page.md +217 -152
- package/src/teamix-evo-design-opentrek/patterns/form-page.md +437 -231
- package/src/teamix-evo-design-opentrek/patterns/list-page.md +220 -292
- package/src/teamix-evo-design-opentrek/patterns/page-types.md +40 -130
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_AGENT RUNTIME.svg +1 -0
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI GATEWAY.svg +1 -0
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI STUDIO.svg +1 -0
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_DEV-2.svg +1 -0
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_LOGO.svg +1 -0
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_OPS.svg +1 -0
- package/src/teamix-evo-design-opentrek/rules/boundaries.rules.json +3 -3
- package/src/teamix-evo-design-opentrek/rules/business-mapping.json +124 -0
- package/src/teamix-evo-design-opentrek/rules/common-components.json +924 -0
- package/src/teamix-evo-design-opentrek/rules/component-specs.json +1083 -0
- package/src/teamix-evo-design-opentrek/rules/design-tokens.css +433 -0
- package/src/teamix-evo-design-opentrek/rules/design-tokens.json +2798 -0
- package/src/teamix-evo-design-opentrek/rules/layout-rules.json +218 -0
- package/src/teamix-evo-design-opentrek/rules/page-flow.json +351 -0
- package/src/teamix-evo-design-opentrek/rules/page-frame.json +241 -0
- package/src/teamix-evo-design-opentrek/rules/page-header-spec.md +123 -0
- package/src/teamix-evo-design-opentrek/rules/page-types.json +206 -0
- package/src/teamix-evo-design-opentrek/rules/sidebar-spec.md +217 -0
- package/src/teamix-evo-design-opentrek/rules/styling.json +188 -0
- package/src/teamix-evo-design-opentrek/rules/token-mapping.md +284 -0
- package/src/teamix-evo-design-uni-manager/SKILL.md +1 -1
- package/src/teamix-evo-design-uni-manager/boundaries.md +3 -3
- package/src/teamix-evo-design-uni-manager/brand.md +1 -1
- package/src/teamix-evo-design-uni-manager/components.md +30 -28
- package/src/teamix-evo-design-uni-manager/foundations.md +21 -21
- package/src/teamix-evo-design-uni-manager/generation-flow.md +3 -1
- package/src/teamix-evo-design-uni-manager/patterns/detail-page.md +1 -1
- package/src/teamix-evo-design-uni-manager/patterns/form-page.md +18 -18
- package/src/teamix-evo-design-uni-manager/patterns/list-page.md +29 -29
- package/src/teamix-evo-design-uni-manager/patterns/page-types.md +11 -11
- package/src/teamix-evo-design-uni-manager/philosophy.md +1 -1
- package/src/teamix-evo-design-uni-manager/rules/boundaries.rules.json +3 -3
- package/src/teamix-evo-manage/SKILL.md +288 -121
- package/src/teamix-evo-upgrade/SKILL.md +298 -0
- package/src/teamix-evo-design-opentrek/patterns/sidebar.md +0 -122
|
@@ -0,0 +1,1339 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>监控详情页 · 标准参考范例 — v7.8.4</title>
|
|
7
|
+
<!--
|
|
8
|
+
★ 监控详情页标准参考范例(Standard Monitor Detail Page Reference)
|
|
9
|
+
─────────────────────────────────────────────────────────────
|
|
10
|
+
页面类型 : DetailPage
|
|
11
|
+
子类型 : MonitorDetailPage(监控面板详情页)
|
|
12
|
+
版本 : v7.7
|
|
13
|
+
场景 : AI 网关实例监控面板 — 请求量 / 成功率 / 延迟 / Token 等核心指标
|
|
14
|
+
─────────────────────────────────────────────────────────────
|
|
15
|
+
布局:TWO_COL(Sidebar + ContentCard 单例容器)
|
|
16
|
+
内容区结构:PageHeader → MetricCard Row → ChartPanel(请求趋势) → Tabs
|
|
17
|
+
─────────────────────────────────────────────────────────────
|
|
18
|
+
组件清单:
|
|
19
|
+
• PageHeader — 面包屑模式 + TimeRangeSelector + 刷新
|
|
20
|
+
• TimeRangeSelector — SegmentedControl(1h/6h/24h/7d/30d)
|
|
21
|
+
• MetricCard — 4 卡横排,标题 + 数值 + 趋势 + 迷你折线图
|
|
22
|
+
• ChartPanel — 全宽请求趋势图 + 2×2 性能指标网格
|
|
23
|
+
• AlertList — 告警记录列表(Critical/Warning/Info)
|
|
24
|
+
• DataTable — 调用明细表
|
|
25
|
+
• Tabs — 性能指标 / 告警记录 / 调用明细
|
|
26
|
+
• Badge — 状态展示
|
|
27
|
+
─────────────────────────────────────────────────────────────
|
|
28
|
+
规范来源:pages/detail-page/SKILL.md v7.8.4
|
|
29
|
+
Tokens 版本:v7.8.4(v7.6 ContentCard 表面规则 + v7.8.4 字体族变量 + MetricCard/ChartPanel/AlertList 组件)
|
|
30
|
+
表面边界:PageHeader / Tabs / Separator 的 border 为「功能性分割线」,不受 v7.6 surface 约束
|
|
31
|
+
-->
|
|
32
|
+
<style>
|
|
33
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
34
|
+
|
|
35
|
+
:root {
|
|
36
|
+
--primary: 218.6 100% 46.7%;
|
|
37
|
+
--primary-foreground: 0 0% 100%;
|
|
38
|
+
--primary-hover: 218.6 100% 42%;
|
|
39
|
+
--primary-click: 218.6 100% 37.5%;
|
|
40
|
+
--gray-primary: 220 9% 7%;
|
|
41
|
+
--gray-primary-foreground: 210 33% 99%;
|
|
42
|
+
--gray-secondary-foreground: 216 4% 26%;
|
|
43
|
+
--muted-foreground: 216 2% 45%;
|
|
44
|
+
--gray-disabled: 210 3% 63%;
|
|
45
|
+
--gray-line: 210 9% 91%;
|
|
46
|
+
--gray-sidebar-accent: 210 12% 93%;
|
|
47
|
+
--gray-muted: 220 18% 97%;
|
|
48
|
+
--gray-white: 0 0% 100%;
|
|
49
|
+
/* Legacy 别名(v7.4 向后兼容) */
|
|
50
|
+
--gray-title: 220 9% 7%;
|
|
51
|
+
--gray-secondary: 216 4% 26%;
|
|
52
|
+
--gray-tertiary: 216 2% 45%;
|
|
53
|
+
--gray-border: 210 9% 91%;
|
|
54
|
+
--gray-fill: 220 18% 97%;
|
|
55
|
+
--gray-bg-dark: 220 18% 97%;
|
|
56
|
+
--gray-bg: 220 18% 97%;
|
|
57
|
+
--success: 142 76% 36%;
|
|
58
|
+
--success-bg: 152 82% 96%;
|
|
59
|
+
--success-border: 142 77% 73%;
|
|
60
|
+
--warning: 40 88% 48%;
|
|
61
|
+
--warning-bg: 43 100% 94%;
|
|
62
|
+
--destructive: 0 72% 51%;
|
|
63
|
+
--destructive-bg: 0 86% 97%;
|
|
64
|
+
--info: 218 100% 47%;
|
|
65
|
+
--info-bg: 214 100% 97%;
|
|
66
|
+
--background: 220 18% 97%;
|
|
67
|
+
--card: 0 0% 100%;
|
|
68
|
+
--card-foreground: 220 9% 7%;
|
|
69
|
+
--border: 210 9% 91%;
|
|
70
|
+
--ring: 218.6 100% 46.7%;
|
|
71
|
+
--sidebar: 220 18% 97%;
|
|
72
|
+
--sidebar-foreground: 220 9% 7%;
|
|
73
|
+
--sidebar-active: 210 12% 93%;
|
|
74
|
+
--sidebar-active-foreground: 220 9% 7%;
|
|
75
|
+
--sidebar-hover: 220 18% 97%;
|
|
76
|
+
--sidebar-item: 220 9% 7%;
|
|
77
|
+
--sidebar-item-muted: 216 4% 26%;
|
|
78
|
+
--sidebar-border: 210 9% 91%;
|
|
79
|
+
--sidebar-group: 216 2% 45%;
|
|
80
|
+
--radius: 1rem;
|
|
81
|
+
--radius-sm: 4px;
|
|
82
|
+
--radius-md: 8px;
|
|
83
|
+
--radius-lg: 12px;
|
|
84
|
+
--gap-xs: 4px;
|
|
85
|
+
--btn-padding-x: 16px;
|
|
86
|
+
--btn-padding-x-sm: 12px;
|
|
87
|
+
--button-gap: 8px;
|
|
88
|
+
--tabs-gap: 24px;
|
|
89
|
+
--card-gap: 16px;
|
|
90
|
+
--page-container-gap: 16px;
|
|
91
|
+
--page-container-padding: 16px;
|
|
92
|
+
--page-container-padding-left: 0px;
|
|
93
|
+
--card-padding-x: 20px;
|
|
94
|
+
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
|
|
95
|
+
--font-size-base: 12px;
|
|
96
|
+
--font-size-lg: 14px;
|
|
97
|
+
--font-size-xl: 16px;
|
|
98
|
+
--font-size-2xl: 18px;
|
|
99
|
+
--font-size-page-header: 18px;
|
|
100
|
+
--font-sans: 'PingFang SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
|
101
|
+
--font-mono: 'SF Mono', Monaco, 'Courier New', monospace;
|
|
102
|
+
--font-weight-normal: 400;
|
|
103
|
+
--font-weight-medium: 500;
|
|
104
|
+
--font-weight-semibold: 600;
|
|
105
|
+
--font-weight-black: 900;
|
|
106
|
+
--input-height: 32px;
|
|
107
|
+
--input-radius: 8px;
|
|
108
|
+
--layout-sidebar-width: 240px;
|
|
109
|
+
--duration-fast: 150ms;
|
|
110
|
+
--duration-normal: 200ms;
|
|
111
|
+
--easing-ease: ease;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
body {
|
|
115
|
+
font-family: var(--font-sans);
|
|
116
|
+
font-size: var(--font-size-base);
|
|
117
|
+
color: hsl(var(--gray-primary));
|
|
118
|
+
background: hsl(var(--background));
|
|
119
|
+
min-height: 100vh;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* ===== Sidebar ===== */
|
|
123
|
+
.sidebar {
|
|
124
|
+
position: fixed; top: 0; left: 0; bottom: 0;
|
|
125
|
+
width: var(--layout-sidebar-width);
|
|
126
|
+
background: hsl(var(--sidebar));
|
|
127
|
+
z-index: 100;
|
|
128
|
+
display: flex; flex-direction: column;
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
transition: width 0.2s ease;
|
|
131
|
+
}
|
|
132
|
+
.sidebar-header {
|
|
133
|
+
height: 56px; padding: 0 16px;
|
|
134
|
+
display: flex; align-items: center; gap: var(--gap-xs);
|
|
135
|
+
flex-shrink: 0;
|
|
136
|
+
}
|
|
137
|
+
.sidebar-logo {
|
|
138
|
+
height: 28px; flex-shrink: 0;
|
|
139
|
+
display: flex; align-items: center;
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
}
|
|
142
|
+
.sidebar-logo img {
|
|
143
|
+
height: 28px; width: auto; display: block;
|
|
144
|
+
}
|
|
145
|
+
.sidebar-logo .collapsed-logo {
|
|
146
|
+
display: none; width: 30px; height: 30px;
|
|
147
|
+
}
|
|
148
|
+
.sidebar-logo .expand-logo {
|
|
149
|
+
display: none; width: 18px; height: 18px;
|
|
150
|
+
color: hsl(var(--muted-foreground));
|
|
151
|
+
}
|
|
152
|
+
.sidebar-collapse-btn {
|
|
153
|
+
margin-left: auto; width: 18px; height: 18px;
|
|
154
|
+
background: none; border: none; padding: 0; cursor: pointer;
|
|
155
|
+
display: flex; align-items: center; justify-content: center;
|
|
156
|
+
color: hsl(var(--muted-foreground)); flex-shrink: 0;
|
|
157
|
+
}
|
|
158
|
+
.sidebar-collapse-btn svg { width: 18px; height: 18px; }
|
|
159
|
+
|
|
160
|
+
/* ===== Sidebar Collapsed State ===== */
|
|
161
|
+
.sidebar.collapsed { width: 68px; overflow: visible; }
|
|
162
|
+
.sidebar.collapsed .sidebar-header { padding: 0 12px; justify-content: center; }
|
|
163
|
+
.sidebar.collapsed .sidebar-logo { cursor: pointer; }
|
|
164
|
+
.sidebar.collapsed .sidebar-logo img { display: none; }
|
|
165
|
+
.sidebar.collapsed .sidebar-logo .collapsed-logo { display: block; }
|
|
166
|
+
.sidebar.collapsed .sidebar-logo:hover .collapsed-logo { display: none; }
|
|
167
|
+
.sidebar.collapsed .sidebar-logo:hover .expand-logo { display: block; }
|
|
168
|
+
.sidebar.collapsed .sidebar-collapse-btn { display: none; }
|
|
169
|
+
.sidebar.collapsed .sidebar-content { padding: 12px 0; overflow-y: auto; overflow-x: hidden; }
|
|
170
|
+
.sidebar.collapsed .sidebar-menu { padding: 0; }
|
|
171
|
+
.sidebar.collapsed .sidebar-menu > li { display: flex; justify-content: center; }
|
|
172
|
+
.sidebar.collapsed .sidebar-menu > li > a.sidebar-menu-item {
|
|
173
|
+
justify-content: center; padding: 0; width: 40px; height: 40px;
|
|
174
|
+
border-radius: var(--radius-md); position: relative; flex-shrink: 0;
|
|
175
|
+
gap: 0;
|
|
176
|
+
}
|
|
177
|
+
.sidebar.collapsed .sidebar-menu > li > a.sidebar-menu-item .item-icon { margin: 0; }
|
|
178
|
+
.sidebar.collapsed .sidebar-menu > li > a > span:not(.item-icon),
|
|
179
|
+
.sidebar.collapsed .sidebar-menu > li > a > .expand-icon,
|
|
180
|
+
.sidebar.collapsed .sidebar-menu-item:not(.item-icon):after { display: none; }
|
|
181
|
+
/* 隐藏文字节点(直接文本) */
|
|
182
|
+
.sidebar.collapsed .sidebar-menu > li > a { font-size: 0; }
|
|
183
|
+
.sidebar.collapsed .sidebar-menu > li > a > .item-icon { font-size: var(--font-size-lg); }
|
|
184
|
+
.sidebar.collapsed .sidebar-sub-menu { display: none !important; }
|
|
185
|
+
.sidebar.collapsed .sidebar-divider { margin: 0 14px; }
|
|
186
|
+
.sidebar.collapsed .sidebar-footer { padding: 12px 10px 16px; justify-content: center; }
|
|
187
|
+
.sidebar.collapsed .sidebar-username,
|
|
188
|
+
.sidebar.collapsed .sidebar-more { display: none; }
|
|
189
|
+
|
|
190
|
+
/* ===== Sidebar Collapsed Flyout (hover 浮层展示二级菜单) ===== */
|
|
191
|
+
.sidebar.collapsed .sidebar-menu > li { position: relative; }
|
|
192
|
+
.sidebar.collapsed .sidebar-menu > li:hover > .sidebar-flyout {
|
|
193
|
+
display: block;
|
|
194
|
+
}
|
|
195
|
+
.sidebar-flyout {
|
|
196
|
+
display: none;
|
|
197
|
+
position: absolute; left: 100%; top: 0;
|
|
198
|
+
min-width: 160px; padding: 4px;
|
|
199
|
+
background: hsl(var(--card));
|
|
200
|
+
border: 1px solid hsl(var(--gray-line));
|
|
201
|
+
border-radius: var(--radius-md);
|
|
202
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
|
203
|
+
z-index: 200;
|
|
204
|
+
}
|
|
205
|
+
.sidebar-flyout .sidebar-menu-item {
|
|
206
|
+
height: 36px; padding: 0 12px;
|
|
207
|
+
font-size: var(--font-size-base) !important;
|
|
208
|
+
font-weight: var(--font-weight-normal);
|
|
209
|
+
color: hsl(var(--gray-primary));
|
|
210
|
+
border-radius: var(--radius-sm);
|
|
211
|
+
white-space: nowrap;
|
|
212
|
+
}
|
|
213
|
+
.sidebar-flyout .sidebar-menu-item:hover {
|
|
214
|
+
background: hsl(var(--gray-muted));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Main content 响应 sidebar 收起 */
|
|
218
|
+
.main-content {
|
|
219
|
+
margin-left: var(--layout-sidebar-width);
|
|
220
|
+
min-height: 100vh;
|
|
221
|
+
transition: margin-left 0.2s ease;
|
|
222
|
+
}
|
|
223
|
+
.sidebar.collapsed ~ .main-content { margin-left: 68px; }
|
|
224
|
+
.sidebar-product {
|
|
225
|
+
font-size: var(--font-size-xl); font-weight: var(--font-weight-black);
|
|
226
|
+
color: hsl(var(--sidebar-foreground)); letter-spacing: -0.025em;
|
|
227
|
+
white-space: nowrap; line-height: 1;
|
|
228
|
+
}
|
|
229
|
+
.sidebar-content {
|
|
230
|
+
flex: 1; overflow-y: auto; padding: 12px 0;
|
|
231
|
+
}
|
|
232
|
+
/* twoLevel 变体:去掉 sidebar-group-label,主菜单之间间距由 li padding 提供;保留 sidebar-group-label 样式以兼容其他页面。 */
|
|
233
|
+
.sidebar-group-label {
|
|
234
|
+
padding: 0 16px; height: 26px; line-height: 26px;
|
|
235
|
+
font-size: var(--font-size-base); font-weight: var(--font-weight-normal);
|
|
236
|
+
color: hsl(var(--sidebar-group));
|
|
237
|
+
}
|
|
238
|
+
.sidebar-group-label:not(:first-child) { margin-top: 16px; }
|
|
239
|
+
.sidebar-menu { list-style: none; padding: 0 8px; display: flex; flex-direction: column; gap: 2px; }
|
|
240
|
+
.sidebar-menu-item {
|
|
241
|
+
display: flex; align-items: center; gap: var(--button-gap);
|
|
242
|
+
padding: 0 12px; height: 40px;
|
|
243
|
+
border-radius: var(--radius-md);
|
|
244
|
+
font-size: var(--font-size-lg); font-weight: var(--font-weight-medium);
|
|
245
|
+
color: hsl(var(--sidebar-item-muted));
|
|
246
|
+
text-decoration: none; cursor: pointer;
|
|
247
|
+
transition: background var(--duration-fast), color var(--duration-fast);
|
|
248
|
+
white-space: nowrap;
|
|
249
|
+
}
|
|
250
|
+
.sidebar-menu-item:hover {
|
|
251
|
+
background: hsl(var(--sidebar-hover));
|
|
252
|
+
color: hsl(var(--sidebar-item));
|
|
253
|
+
}
|
|
254
|
+
.sidebar-menu-item.active {
|
|
255
|
+
background: hsl(var(--sidebar-active));
|
|
256
|
+
color: hsl(var(--sidebar-active-foreground));
|
|
257
|
+
}
|
|
258
|
+
.sidebar-menu-item .item-icon {
|
|
259
|
+
width: 16px; height: 16px; flex-shrink: 0;
|
|
260
|
+
display: flex; align-items: center; justify-content: center;
|
|
261
|
+
}
|
|
262
|
+
.sidebar-menu-item .expand-icon {
|
|
263
|
+
margin-left: auto; width: 16px; height: 16px;
|
|
264
|
+
transition: transform var(--duration-fast);
|
|
265
|
+
}
|
|
266
|
+
.sidebar-menu-item.expanded .expand-icon { transform: rotate(90deg); }
|
|
267
|
+
.sidebar-sub-menu {
|
|
268
|
+
list-style: none; padding: 0 0 0 24px;
|
|
269
|
+
display: none;
|
|
270
|
+
}
|
|
271
|
+
/* twoLevel 变体:子菜单与主菜单在同一列上下对齐。sub-menu 不再加左 padding,由子菜单项自身 padding-left = 12+16+8 = 36px 代替主菜单的 "padding+icon+gap",使文字起点 x 坐标与主菜单一致。 */
|
|
272
|
+
.sidebar-menu > li > .sidebar-sub-menu { padding-left: 0; }
|
|
273
|
+
.sidebar-sub-menu.show { display: block; }
|
|
274
|
+
.sidebar-sub-menu .sidebar-menu-item {
|
|
275
|
+
height: 36px; font-size: var(--font-size-base); font-weight: var(--font-weight-normal);
|
|
276
|
+
/* twoLevel 变体对齐规则:36px = 主菜单 padding-left(12) + item-icon(16) + gap(8),使文字与主菜单文字上下对齐。 */
|
|
277
|
+
padding-left: 36px;
|
|
278
|
+
}
|
|
279
|
+
.sidebar-divider {
|
|
280
|
+
height: 1px; background: hsl(var(--sidebar-border));
|
|
281
|
+
margin: 0 12px; flex-shrink: 0;
|
|
282
|
+
}
|
|
283
|
+
.sidebar-footer {
|
|
284
|
+
flex-shrink: 0; display: flex; align-items: center;
|
|
285
|
+
gap: var(--button-gap); padding: 12px 12px 16px 24px;
|
|
286
|
+
}
|
|
287
|
+
.sidebar-avatar {
|
|
288
|
+
width: 28px; height: 28px; border-radius: 50%;
|
|
289
|
+
background: hsl(var(--sidebar-active));
|
|
290
|
+
display: flex; align-items: center; justify-content: center;
|
|
291
|
+
font-size: var(--font-size-base); font-weight: 600;
|
|
292
|
+
color: hsl(var(--gray-primary)); flex-shrink: 0;
|
|
293
|
+
}
|
|
294
|
+
.sidebar-username {
|
|
295
|
+
flex: 1; font-size: var(--font-size-lg); font-weight: var(--font-weight-medium);
|
|
296
|
+
color: hsl(var(--sidebar-item)); overflow: hidden;
|
|
297
|
+
text-overflow: ellipsis; white-space: nowrap;
|
|
298
|
+
}
|
|
299
|
+
.sidebar-more {
|
|
300
|
+
width: 28px; height: 28px; border-radius: var(--radius-sm);
|
|
301
|
+
border: none; background: transparent; cursor: pointer;
|
|
302
|
+
display: flex; align-items: center; justify-content: center;
|
|
303
|
+
color: hsl(var(--muted-foreground));
|
|
304
|
+
}
|
|
305
|
+
.sidebar-more:hover { background: hsl(var(--sidebar-hover)); color: hsl(var(--gray-primary)); }
|
|
306
|
+
|
|
307
|
+
/* ===== Main Content ===== */
|
|
308
|
+
.page-container {
|
|
309
|
+
padding: var(--page-container-padding) var(--page-container-padding) var(--page-container-padding) var(--page-container-padding-left);
|
|
310
|
+
}
|
|
311
|
+
.card {
|
|
312
|
+
background: hsl(var(--card));
|
|
313
|
+
border-radius: var(--radius-lg);
|
|
314
|
+
/* v7.6:去掉 1px border,使用最弱阴影 var(--shadow-sm) */
|
|
315
|
+
box-shadow: var(--shadow-sm, 0 1px 2px rgba(0,0,0,0.05));
|
|
316
|
+
overflow: hidden;
|
|
317
|
+
display: flex; flex-direction: column;
|
|
318
|
+
min-height: calc(100vh - 40px);
|
|
319
|
+
padding: 0 var(--card-padding-x);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* ===== PageHeader — Breadcrumb Mode ===== */
|
|
323
|
+
/* L2 面包屑模式:无 border、无 background、无 box-shadow;高度 64px,padding 由外层 Card 提供 */
|
|
324
|
+
.page-header {
|
|
325
|
+
height: 64px; padding: 0;
|
|
326
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
327
|
+
flex-shrink: 0;
|
|
328
|
+
}
|
|
329
|
+
.page-header-left { display: flex; align-items: center; }
|
|
330
|
+
.breadcrumb {
|
|
331
|
+
display: flex; align-items: center; gap: 4px;
|
|
332
|
+
font-size: var(--font-size-page-header); font-weight: var(--font-weight-medium);
|
|
333
|
+
color: hsl(var(--gray-secondary-foreground));
|
|
334
|
+
}
|
|
335
|
+
.breadcrumb a { color: hsl(var(--gray-secondary-foreground)); text-decoration: none; }
|
|
336
|
+
.breadcrumb a:hover { color: hsl(var(--gray-primary)); }
|
|
337
|
+
.breadcrumb .separator { color: hsl(var(--muted-foreground)); display: flex; align-items: center; }
|
|
338
|
+
.breadcrumb .separator svg { width: 20px; height: 20px; }
|
|
339
|
+
.breadcrumb .current { color: hsl(var(--gray-primary)); }
|
|
340
|
+
.page-header-right { display: flex; align-items: center; gap: var(--button-gap); }
|
|
341
|
+
|
|
342
|
+
/* ===== Buttons ===== */
|
|
343
|
+
.btn {
|
|
344
|
+
display: inline-flex; align-items: center; justify-content: center; gap: var(--gap-xs);
|
|
345
|
+
height: var(--input-height); padding: 0 var(--btn-padding-x);
|
|
346
|
+
border-radius: var(--radius-md); font-size: var(--font-size-base);
|
|
347
|
+
font-weight: var(--font-weight-medium); border: none; cursor: pointer;
|
|
348
|
+
transition: background var(--duration-fast), opacity var(--duration-fast);
|
|
349
|
+
white-space: nowrap;
|
|
350
|
+
}
|
|
351
|
+
.btn-primary { background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
|
|
352
|
+
.btn-primary:hover { background: hsl(var(--primary-hover)); }
|
|
353
|
+
.btn-outline { background: hsl(var(--card)); color: hsl(var(--gray-primary)); border: 1px solid hsl(var(--gray-line)); }
|
|
354
|
+
.btn-outline:hover { background: hsl(var(--gray-muted)); }
|
|
355
|
+
.btn-ghost { background: transparent; color: hsl(var(--gray-primary)); }
|
|
356
|
+
.btn-ghost:hover { background: hsl(var(--gray-muted)); }
|
|
357
|
+
.btn-icon { width: var(--input-height); padding: 0; }
|
|
358
|
+
|
|
359
|
+
/* ===== TimeRangeSelector — SegmentedControl ===== */
|
|
360
|
+
.time-range-selector {
|
|
361
|
+
display: inline-flex; align-items: center;
|
|
362
|
+
height: 32px; padding: 2px;
|
|
363
|
+
background: hsl(var(--gray-muted));
|
|
364
|
+
border-radius: var(--radius-md);
|
|
365
|
+
gap: 2px;
|
|
366
|
+
}
|
|
367
|
+
.time-range-item {
|
|
368
|
+
height: 28px; padding: 0 12px;
|
|
369
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
370
|
+
font-size: var(--font-size-base); font-weight: var(--font-weight-medium);
|
|
371
|
+
color: hsl(var(--gray-secondary-foreground));
|
|
372
|
+
background: transparent; border: none; cursor: pointer;
|
|
373
|
+
border-radius: var(--radius-sm);
|
|
374
|
+
transition: background var(--duration-fast), color var(--duration-fast);
|
|
375
|
+
white-space: nowrap;
|
|
376
|
+
}
|
|
377
|
+
.time-range-item:hover { color: hsl(var(--gray-primary)); }
|
|
378
|
+
.time-range-item.active {
|
|
379
|
+
background: hsl(var(--primary));
|
|
380
|
+
color: hsl(var(--primary-foreground));
|
|
381
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* ===== MetricCard Row ===== */
|
|
385
|
+
.metric-row {
|
|
386
|
+
display: grid;
|
|
387
|
+
grid-template-columns: repeat(4, 1fr);
|
|
388
|
+
gap: 12px;
|
|
389
|
+
padding: 16px 0;
|
|
390
|
+
}
|
|
391
|
+
.metric-card {
|
|
392
|
+
height: 96px;
|
|
393
|
+
padding: 12px 16px;
|
|
394
|
+
background: hsl(var(--gray-muted));
|
|
395
|
+
border-radius: var(--radius-md);
|
|
396
|
+
display: flex; flex-direction: column;
|
|
397
|
+
position: relative; overflow: hidden;
|
|
398
|
+
}
|
|
399
|
+
.metric-card-head {
|
|
400
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
401
|
+
margin-bottom: 4px;
|
|
402
|
+
}
|
|
403
|
+
.metric-card-title {
|
|
404
|
+
font-size: var(--font-size-base); font-weight: var(--font-weight-normal);
|
|
405
|
+
color: hsl(var(--gray-secondary-foreground));
|
|
406
|
+
}
|
|
407
|
+
.metric-card-icon {
|
|
408
|
+
width: 16px; height: 16px;
|
|
409
|
+
color: hsl(var(--muted-foreground));
|
|
410
|
+
}
|
|
411
|
+
.metric-card-body {
|
|
412
|
+
display: flex; align-items: baseline; gap: 8px;
|
|
413
|
+
flex: 1;
|
|
414
|
+
}
|
|
415
|
+
.metric-card-value {
|
|
416
|
+
font-size: 24px; font-weight: var(--font-weight-semibold);
|
|
417
|
+
color: hsl(var(--gray-primary)); line-height: 1.2;
|
|
418
|
+
letter-spacing: -0.01em;
|
|
419
|
+
}
|
|
420
|
+
.metric-card-trend {
|
|
421
|
+
display: inline-flex; align-items: center; gap: 2px;
|
|
422
|
+
font-size: var(--font-size-base); font-weight: var(--font-weight-medium);
|
|
423
|
+
}
|
|
424
|
+
.metric-card-trend.up { color: hsl(var(--success)); }
|
|
425
|
+
.metric-card-trend.down { color: hsl(var(--destructive)); }
|
|
426
|
+
.metric-card-trend.down-good { color: hsl(var(--success)); } /* 延迟下降是好事 */
|
|
427
|
+
.metric-card-trend.flat { color: hsl(var(--muted-foreground)); }
|
|
428
|
+
.metric-card-spark {
|
|
429
|
+
position: absolute; left: 0; right: 0; bottom: 0;
|
|
430
|
+
height: 24px; width: 100%;
|
|
431
|
+
pointer-events: none;
|
|
432
|
+
}
|
|
433
|
+
.metric-card-spark svg { width: 100%; height: 100%; display: block; }
|
|
434
|
+
|
|
435
|
+
/* ===== ChartPanel ===== */
|
|
436
|
+
.chart-panel {
|
|
437
|
+
background: hsl(var(--card));
|
|
438
|
+
border: 1px solid hsl(var(--gray-line));
|
|
439
|
+
border-radius: var(--radius-md);
|
|
440
|
+
padding: 16px;
|
|
441
|
+
margin-bottom: 16px;
|
|
442
|
+
display: flex; flex-direction: column;
|
|
443
|
+
}
|
|
444
|
+
.chart-panel-full { height: 280px; }
|
|
445
|
+
.chart-panel-grid-cell { height: 220px; }
|
|
446
|
+
.chart-panel-head {
|
|
447
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
448
|
+
margin-bottom: 8px; flex-shrink: 0;
|
|
449
|
+
}
|
|
450
|
+
.chart-panel-title {
|
|
451
|
+
font-size: var(--font-size-lg); font-weight: var(--font-weight-medium);
|
|
452
|
+
color: hsl(var(--gray-primary));
|
|
453
|
+
}
|
|
454
|
+
.chart-panel-actions {
|
|
455
|
+
display: flex; align-items: center; gap: 8px;
|
|
456
|
+
}
|
|
457
|
+
.chart-panel-link {
|
|
458
|
+
font-size: var(--font-size-base); color: hsl(var(--primary));
|
|
459
|
+
background: transparent; border: none; cursor: pointer;
|
|
460
|
+
padding: 4px 8px; border-radius: var(--radius-sm);
|
|
461
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
462
|
+
}
|
|
463
|
+
.chart-panel-link:hover { background: hsl(var(--info-bg)); }
|
|
464
|
+
.chart-panel-body {
|
|
465
|
+
flex: 1; min-height: 0; position: relative;
|
|
466
|
+
}
|
|
467
|
+
.chart-panel-body svg { width: 100%; height: 100%; display: block; }
|
|
468
|
+
.chart-axis-label {
|
|
469
|
+
font-size: 11px;
|
|
470
|
+
fill: hsl(var(--muted-foreground));
|
|
471
|
+
}
|
|
472
|
+
.chart-grid-line {
|
|
473
|
+
stroke: hsl(var(--gray-line));
|
|
474
|
+
stroke-width: 0.5;
|
|
475
|
+
stroke-dasharray: 3 3;
|
|
476
|
+
}
|
|
477
|
+
.chart-area-fill { fill: hsl(var(--primary) / 0.08); }
|
|
478
|
+
.chart-line { fill: none; stroke: hsl(var(--primary)); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
|
|
479
|
+
.chart-line-success { stroke: hsl(var(--success)); }
|
|
480
|
+
.chart-line-warning { stroke: hsl(var(--warning)); }
|
|
481
|
+
.chart-line-destructive { stroke: hsl(var(--destructive)); }
|
|
482
|
+
.chart-bar { fill: hsl(var(--primary)); }
|
|
483
|
+
.chart-bar-alt { fill: hsl(var(--primary) / 0.65); }
|
|
484
|
+
|
|
485
|
+
.chart-grid-2 {
|
|
486
|
+
display: grid;
|
|
487
|
+
grid-template-columns: repeat(2, 1fr);
|
|
488
|
+
gap: 16px;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* ===== Tabs ===== */
|
|
492
|
+
.tabs {
|
|
493
|
+
border-bottom: 1px solid hsl(var(--gray-line));
|
|
494
|
+
padding-top: 4px;
|
|
495
|
+
}
|
|
496
|
+
.tabs-list {
|
|
497
|
+
display: flex; gap: var(--tabs-gap); list-style: none;
|
|
498
|
+
}
|
|
499
|
+
.tab-item {
|
|
500
|
+
padding: 10px 0; font-size: var(--font-size-lg);
|
|
501
|
+
color: hsl(var(--gray-secondary-foreground)); cursor: pointer;
|
|
502
|
+
border-bottom: 2px solid transparent;
|
|
503
|
+
transition: color var(--duration-fast), border-color var(--duration-fast);
|
|
504
|
+
font-weight: var(--font-weight-normal);
|
|
505
|
+
}
|
|
506
|
+
.tab-item:hover { color: hsl(var(--gray-primary)); }
|
|
507
|
+
.tab-item.active {
|
|
508
|
+
color: hsl(var(--primary));
|
|
509
|
+
border-bottom-color: hsl(var(--primary));
|
|
510
|
+
font-weight: var(--font-weight-medium);
|
|
511
|
+
}
|
|
512
|
+
.tab-content { padding: 20px 0; }
|
|
513
|
+
.tab-content.hidden { display: none; }
|
|
514
|
+
|
|
515
|
+
/* ===== AlertList ===== */
|
|
516
|
+
.alert-list {
|
|
517
|
+
display: flex; flex-direction: column;
|
|
518
|
+
}
|
|
519
|
+
.alert-item {
|
|
520
|
+
display: flex; align-items: flex-start; gap: 12px;
|
|
521
|
+
min-height: 56px; padding: 12px 16px;
|
|
522
|
+
border-bottom: 1px solid hsl(var(--gray-line));
|
|
523
|
+
background: hsl(var(--card));
|
|
524
|
+
transition: background var(--duration-fast);
|
|
525
|
+
}
|
|
526
|
+
.alert-item:last-child { border-bottom: none; }
|
|
527
|
+
.alert-item:hover { background: hsl(var(--gray-muted)); }
|
|
528
|
+
.alert-dot {
|
|
529
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
530
|
+
flex-shrink: 0; margin-top: 6px;
|
|
531
|
+
}
|
|
532
|
+
.alert-dot.critical { background: hsl(var(--destructive)); box-shadow: 0 0 0 3px hsl(var(--destructive) / 0.15); }
|
|
533
|
+
.alert-dot.warning { background: hsl(var(--warning)); box-shadow: 0 0 0 3px hsl(var(--warning) / 0.15); }
|
|
534
|
+
.alert-dot.info { background: hsl(var(--info)); box-shadow: 0 0 0 3px hsl(var(--info) / 0.15); }
|
|
535
|
+
.alert-content { flex: 1; min-width: 0; }
|
|
536
|
+
.alert-head {
|
|
537
|
+
display: flex; align-items: center; gap: 8px;
|
|
538
|
+
margin-bottom: 4px;
|
|
539
|
+
}
|
|
540
|
+
.alert-severity {
|
|
541
|
+
font-size: 11px; font-weight: var(--font-weight-medium);
|
|
542
|
+
padding: 1px 6px; border-radius: var(--radius-sm);
|
|
543
|
+
letter-spacing: 0.02em;
|
|
544
|
+
}
|
|
545
|
+
.alert-severity.critical { background: hsl(var(--destructive-bg)); color: hsl(var(--destructive)); }
|
|
546
|
+
.alert-severity.warning { background: hsl(var(--warning-bg)); color: hsl(var(--warning)); }
|
|
547
|
+
.alert-severity.info { background: hsl(var(--info-bg)); color: hsl(var(--info)); }
|
|
548
|
+
.alert-title {
|
|
549
|
+
font-size: 13px; font-weight: var(--font-weight-medium);
|
|
550
|
+
color: hsl(var(--gray-primary));
|
|
551
|
+
}
|
|
552
|
+
.alert-time {
|
|
553
|
+
font-size: var(--font-size-base); color: hsl(var(--muted-foreground));
|
|
554
|
+
margin-left: auto;
|
|
555
|
+
}
|
|
556
|
+
.alert-desc {
|
|
557
|
+
font-size: var(--font-size-base); color: hsl(var(--gray-secondary-foreground));
|
|
558
|
+
line-height: 1.5;
|
|
559
|
+
display: -webkit-box;
|
|
560
|
+
-webkit-line-clamp: 2;
|
|
561
|
+
-webkit-box-orient: vertical;
|
|
562
|
+
overflow: hidden;
|
|
563
|
+
}
|
|
564
|
+
.alert-empty {
|
|
565
|
+
padding: 48px 16px; text-align: center;
|
|
566
|
+
color: hsl(var(--gray-secondary-foreground));
|
|
567
|
+
display: flex; flex-direction: column; align-items: center; gap: 12px;
|
|
568
|
+
}
|
|
569
|
+
.alert-empty-icon {
|
|
570
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
571
|
+
background: hsl(var(--success-bg));
|
|
572
|
+
color: hsl(var(--success));
|
|
573
|
+
display: flex; align-items: center; justify-content: center;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/* ===== DataTable ===== */
|
|
577
|
+
.data-table {
|
|
578
|
+
width: 100%;
|
|
579
|
+
border-collapse: collapse;
|
|
580
|
+
font-size: var(--font-size-base);
|
|
581
|
+
border: 1px solid hsl(var(--gray-line));
|
|
582
|
+
border-radius: var(--radius-md);
|
|
583
|
+
overflow: hidden;
|
|
584
|
+
}
|
|
585
|
+
.data-table thead tr {
|
|
586
|
+
height: 36px;
|
|
587
|
+
background: hsl(var(--gray-muted));
|
|
588
|
+
}
|
|
589
|
+
.data-table th {
|
|
590
|
+
text-align: left;
|
|
591
|
+
padding: 0 12px;
|
|
592
|
+
font-size: var(--font-size-base);
|
|
593
|
+
font-weight: var(--font-weight-medium);
|
|
594
|
+
color: hsl(var(--gray-secondary-foreground));
|
|
595
|
+
border-bottom: 1px solid hsl(var(--gray-line));
|
|
596
|
+
}
|
|
597
|
+
.data-table tbody tr {
|
|
598
|
+
height: 40px;
|
|
599
|
+
transition: background var(--duration-fast);
|
|
600
|
+
}
|
|
601
|
+
.data-table tbody tr:hover { background: hsl(var(--gray-muted)); }
|
|
602
|
+
.data-table tbody tr + tr td { border-top: 1px solid hsl(var(--gray-line)); }
|
|
603
|
+
.data-table td {
|
|
604
|
+
padding: 0 12px;
|
|
605
|
+
color: hsl(var(--gray-primary));
|
|
606
|
+
vertical-align: middle;
|
|
607
|
+
}
|
|
608
|
+
.data-table td.numeric { font-variant-numeric: tabular-nums; }
|
|
609
|
+
.data-table td.muted { color: hsl(var(--muted-foreground)); }
|
|
610
|
+
|
|
611
|
+
/* ===== Badge ===== */
|
|
612
|
+
.badge {
|
|
613
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
614
|
+
padding: 2px 8px; border-radius: var(--radius-sm);
|
|
615
|
+
font-size: var(--font-size-base); font-weight: var(--font-weight-medium);
|
|
616
|
+
line-height: 1.5;
|
|
617
|
+
}
|
|
618
|
+
.badge-success { background: hsl(var(--success-bg)); color: hsl(var(--success)); }
|
|
619
|
+
.badge-warning { background: hsl(var(--warning-bg)); color: hsl(var(--warning)); }
|
|
620
|
+
.badge-destructive { background: hsl(var(--destructive-bg)); color: hsl(var(--destructive)); }
|
|
621
|
+
.badge-dot {
|
|
622
|
+
width: 6px; height: 6px; border-radius: 50%;
|
|
623
|
+
}
|
|
624
|
+
.badge-success .badge-dot { background: hsl(var(--success)); }
|
|
625
|
+
.badge-warning .badge-dot { background: hsl(var(--warning)); }
|
|
626
|
+
.badge-destructive .badge-dot { background: hsl(var(--destructive)); }
|
|
627
|
+
|
|
628
|
+
/* ===== Responsive ===== */
|
|
629
|
+
@media (max-width: 1199px) {
|
|
630
|
+
.metric-row { grid-template-columns: repeat(2, 1fr); }
|
|
631
|
+
}
|
|
632
|
+
@media (max-width: 768px) {
|
|
633
|
+
.metric-row { grid-template-columns: 1fr; }
|
|
634
|
+
.chart-grid-2 { grid-template-columns: 1fr; }
|
|
635
|
+
}
|
|
636
|
+
</style>
|
|
637
|
+
</head>
|
|
638
|
+
<body>
|
|
639
|
+
|
|
640
|
+
<!-- ===== Sidebar ===== -->
|
|
641
|
+
<aside class="sidebar">
|
|
642
|
+
<div class="sidebar-header">
|
|
643
|
+
<div class="sidebar-logo" id="sidebarLogoArea">
|
|
644
|
+
<img src="./_assets/op-ai-gateway-logo.svg" alt="OP AI Gateway" />
|
|
645
|
+
<img class="collapsed-logo" src="./_assets/op-logo-collapsed.png" alt="OP" />
|
|
646
|
+
<svg class="expand-logo" viewBox="0 0 1024 1024" fill="currentColor" width="18" height="18"><path d="M151.68 616.96l82.56-82.56c12.8-12.8 12.8-32.64 0-45.44L151.68 406.4c-19.84-19.84-54.4-5.76-54.4 22.4v165.76c-0.64 28.16 33.92 42.88 54.4 22.4zM128.64 288h768c14.08 0 32-14.08 32-32s-17.92-32-32-32h-768c-14.08 0-32 14.08-32 32s17.92 32 32 32zM896.64 736h-768c-14.08 0-32 14.08-32 32s17.92 32 32 32h768c14.08 0 32-14.08 32-32s-17.28-32-32-32zM896.64 480h-480c-14.08 0-32 14.08-32 32s17.92 32 32 32h480c14.08 0 32-14.08 32-32s-17.28-32-32-32z"/></svg>
|
|
647
|
+
</div>
|
|
648
|
+
<button class="sidebar-collapse-btn" title="收起侧边栏">
|
|
649
|
+
<svg viewBox="0 0 1024 1024" fill="currentColor" width="18" height="18"><path d="M874.24 406.4l-82.56 82.56c-12.8 12.8-12.8 32.64 0 45.44l82.56 82.56c19.84 19.84 54.4 5.76 54.4-22.4V428.8c0-28.16-33.92-42.88-54.4-22.4zM128.64 288h768c14.08 0 32-14.08 32-32s-17.92-32-32-32h-768c-14.08 0-32 14.08-32 32s17.92 32 32 32zM896.64 736h-768c-14.08 0-32 14.08-32 32s17.92 32 32 32h768c14.08 0 32-14.08 32-32s-17.28-32-32-32zM128.64 544h480c14.08 0 32-14.08 32-32s-17.92-32-32-32h-480c-14.08 0-32 14.08-32 32s17.92 32 32 32z"/></svg>
|
|
650
|
+
</button>
|
|
651
|
+
</div>
|
|
652
|
+
|
|
653
|
+
<!-- sidebar:按 PRD 导航结构,L1 为一级菜单,L2 为二级子菜单。 -->
|
|
654
|
+
<div class="sidebar-content">
|
|
655
|
+
<ul class="sidebar-menu">
|
|
656
|
+
<!-- L1: AI 网关(叶子菜单,当前活跃) -->
|
|
657
|
+
<li><a class="sidebar-menu-item active" href="#">
|
|
658
|
+
<span class="item-icon">
|
|
659
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
|
660
|
+
</span>
|
|
661
|
+
AI 网关
|
|
662
|
+
</a></li>
|
|
663
|
+
|
|
664
|
+
<!-- L1: API 网关(父菜单) -->
|
|
665
|
+
<li>
|
|
666
|
+
<a class="sidebar-menu-item expanded" href="#" data-toggle="api-gateway">
|
|
667
|
+
<span class="item-icon">
|
|
668
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
|
|
669
|
+
</span>
|
|
670
|
+
API 网关
|
|
671
|
+
<span class="expand-icon">
|
|
672
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
|
|
673
|
+
</span>
|
|
674
|
+
</a>
|
|
675
|
+
<ul class="sidebar-sub-menu show" id="api-gateway">
|
|
676
|
+
<li><a class="sidebar-menu-item" href="#">实例管理</a></li>
|
|
677
|
+
<li><a class="sidebar-menu-item" href="#">共享实例管理</a></li>
|
|
678
|
+
</ul>
|
|
679
|
+
<div class="sidebar-flyout">
|
|
680
|
+
<a class="sidebar-menu-item" href="#">实例管理</a>
|
|
681
|
+
<a class="sidebar-menu-item" href="#">共享实例管理</a>
|
|
682
|
+
</div>
|
|
683
|
+
</li>
|
|
684
|
+
|
|
685
|
+
<!-- L1: 云原生网关(父菜单) -->
|
|
686
|
+
<li>
|
|
687
|
+
<a class="sidebar-menu-item" href="#" data-toggle="cloud-native-gw">
|
|
688
|
+
<span class="item-icon">
|
|
689
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 10h-1.26A8 8 0 109 20h9a5 5 0 000-10z"/></svg>
|
|
690
|
+
</span>
|
|
691
|
+
云原生网关
|
|
692
|
+
<span class="expand-icon">
|
|
693
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
|
|
694
|
+
</span>
|
|
695
|
+
</a>
|
|
696
|
+
<ul class="sidebar-sub-menu" id="cloud-native-gw">
|
|
697
|
+
<li><a class="sidebar-menu-item" href="#">实例列表</a></li>
|
|
698
|
+
<li><a class="sidebar-menu-item" href="#">Nginx Ingress 迁移</a></li>
|
|
699
|
+
</ul>
|
|
700
|
+
<div class="sidebar-flyout">
|
|
701
|
+
<a class="sidebar-menu-item" href="#">实例列表</a>
|
|
702
|
+
<a class="sidebar-menu-item" href="#">Nginx Ingress 迁移</a>
|
|
703
|
+
</div>
|
|
704
|
+
</li>
|
|
705
|
+
|
|
706
|
+
<!-- L1: 集群管理(叶子菜单) -->
|
|
707
|
+
<li><a class="sidebar-menu-item" href="#">
|
|
708
|
+
<span class="item-icon">
|
|
709
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>
|
|
710
|
+
</span>
|
|
711
|
+
集群管理
|
|
712
|
+
</a></li>
|
|
713
|
+
|
|
714
|
+
<!-- L1: 级联管理(父菜单) -->
|
|
715
|
+
<li>
|
|
716
|
+
<a class="sidebar-menu-item" href="#" data-toggle="cascade-mgmt">
|
|
717
|
+
<span class="item-icon">
|
|
718
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6v12M12 3v18M20 6v12M2 6h4M10 3h4M18 6h4M2 12h4M10 9h4M18 12h4M2 18h4M10 21h4M18 18h4"/></svg>
|
|
719
|
+
</span>
|
|
720
|
+
级联管理
|
|
721
|
+
<span class="expand-icon">
|
|
722
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
|
|
723
|
+
</span>
|
|
724
|
+
</a>
|
|
725
|
+
<ul class="sidebar-sub-menu" id="cascade-mgmt">
|
|
726
|
+
<li><a class="sidebar-menu-item" href="#">级联网关</a></li>
|
|
727
|
+
<li><a class="sidebar-menu-item" href="#">级联链路</a></li>
|
|
728
|
+
</ul>
|
|
729
|
+
<div class="sidebar-flyout">
|
|
730
|
+
<a class="sidebar-menu-item" href="#">级联网关</a>
|
|
731
|
+
<a class="sidebar-menu-item" href="#">级联链路</a>
|
|
732
|
+
</div>
|
|
733
|
+
</li>
|
|
734
|
+
|
|
735
|
+
<!-- L1: 容灾管理(父菜单) -->
|
|
736
|
+
<li>
|
|
737
|
+
<a class="sidebar-menu-item" href="#" data-toggle="disaster-mgmt">
|
|
738
|
+
<span class="item-icon">
|
|
739
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
|
740
|
+
</span>
|
|
741
|
+
容灾管理
|
|
742
|
+
<span class="expand-icon">
|
|
743
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
|
|
744
|
+
</span>
|
|
745
|
+
</a>
|
|
746
|
+
<ul class="sidebar-sub-menu" id="disaster-mgmt">
|
|
747
|
+
<li><a class="sidebar-menu-item" href="#">容灾网关实例</a></li>
|
|
748
|
+
</ul>
|
|
749
|
+
<div class="sidebar-flyout">
|
|
750
|
+
<a class="sidebar-menu-item" href="#">容灾网关实例</a>
|
|
751
|
+
</div>
|
|
752
|
+
</li>
|
|
753
|
+
|
|
754
|
+
<!-- L1: 认证授权(父菜单) -->
|
|
755
|
+
<li>
|
|
756
|
+
<a class="sidebar-menu-item" href="#" data-toggle="auth-mgmt">
|
|
757
|
+
<span class="item-icon">
|
|
758
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
|
|
759
|
+
</span>
|
|
760
|
+
认证授权
|
|
761
|
+
<span class="expand-icon">
|
|
762
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
|
|
763
|
+
</span>
|
|
764
|
+
</a>
|
|
765
|
+
<ul class="sidebar-sub-menu" id="auth-mgmt">
|
|
766
|
+
<li><a class="sidebar-menu-item" href="#">用户管理</a></li>
|
|
767
|
+
<li><a class="sidebar-menu-item" href="#">角色管理</a></li>
|
|
768
|
+
<li><a class="sidebar-menu-item" href="#">权限插件</a></li>
|
|
769
|
+
<li><a class="sidebar-menu-item" href="#">访问凭证</a></li>
|
|
770
|
+
</ul>
|
|
771
|
+
<div class="sidebar-flyout">
|
|
772
|
+
<a class="sidebar-menu-item" href="#">用户管理</a>
|
|
773
|
+
<a class="sidebar-menu-item" href="#">角色管理</a>
|
|
774
|
+
<a class="sidebar-menu-item" href="#">权限插件</a>
|
|
775
|
+
<a class="sidebar-menu-item" href="#">访问凭证</a>
|
|
776
|
+
</div>
|
|
777
|
+
</li>
|
|
778
|
+
</ul>
|
|
779
|
+
</div>
|
|
780
|
+
|
|
781
|
+
<div class="sidebar-divider"></div>
|
|
782
|
+
<div class="sidebar-footer">
|
|
783
|
+
<div class="sidebar-avatar">A</div>
|
|
784
|
+
<span class="sidebar-username">admin</span>
|
|
785
|
+
<button class="sidebar-more">
|
|
786
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>
|
|
787
|
+
</button>
|
|
788
|
+
</div>
|
|
789
|
+
</aside>
|
|
790
|
+
|
|
791
|
+
<!-- ===== Main Content ===== -->
|
|
792
|
+
<main class="main-content">
|
|
793
|
+
<div class="page-container">
|
|
794
|
+
<div class="card">
|
|
795
|
+
|
|
796
|
+
<!-- ============ PageHeader — 面包屑 + 时间范围 + 刷新 ============ -->
|
|
797
|
+
<div class="page-header">
|
|
798
|
+
<div class="page-header-left">
|
|
799
|
+
<nav class="breadcrumb">
|
|
800
|
+
<a href="#">实例管理</a>
|
|
801
|
+
<span class="separator"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg></span>
|
|
802
|
+
<a href="#">ai-gateway-prod</a>
|
|
803
|
+
<span class="separator"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg></span>
|
|
804
|
+
<span class="current">监控</span>
|
|
805
|
+
</nav>
|
|
806
|
+
</div>
|
|
807
|
+
<div class="page-header-right">
|
|
808
|
+
<!-- TimeRangeSelector -->
|
|
809
|
+
<div class="time-range-selector" role="tablist" aria-label="时间范围">
|
|
810
|
+
<button class="time-range-item" onclick="selectTimeRange(this)">1h</button>
|
|
811
|
+
<button class="time-range-item" onclick="selectTimeRange(this)">6h</button>
|
|
812
|
+
<button class="time-range-item active" onclick="selectTimeRange(this)">24h</button>
|
|
813
|
+
<button class="time-range-item" onclick="selectTimeRange(this)">7d</button>
|
|
814
|
+
<button class="time-range-item" onclick="selectTimeRange(this)">30d</button>
|
|
815
|
+
</div>
|
|
816
|
+
<button class="btn btn-outline" onclick="refresh()">
|
|
817
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/></svg>
|
|
818
|
+
刷新
|
|
819
|
+
</button>
|
|
820
|
+
</div>
|
|
821
|
+
</div>
|
|
822
|
+
|
|
823
|
+
<!-- ============ MetricCard Row — 4 卡横排 ============ -->
|
|
824
|
+
<div class="metric-row">
|
|
825
|
+
|
|
826
|
+
<!-- 请求总量 -->
|
|
827
|
+
<div class="metric-card">
|
|
828
|
+
<div class="metric-card-head">
|
|
829
|
+
<span class="metric-card-title">请求总量</span>
|
|
830
|
+
<svg class="metric-card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
|
|
831
|
+
</div>
|
|
832
|
+
<div class="metric-card-body">
|
|
833
|
+
<span class="metric-card-value">12,583</span>
|
|
834
|
+
<span class="metric-card-trend up">
|
|
835
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
|
|
836
|
+
23.5%
|
|
837
|
+
</span>
|
|
838
|
+
</div>
|
|
839
|
+
<div class="metric-card-spark">
|
|
840
|
+
<svg viewBox="0 0 100 24" preserveAspectRatio="none">
|
|
841
|
+
<polyline class="chart-line" points="0,18 8,16 16,17 24,14 32,15 40,11 48,13 56,9 64,10 72,7 80,8 88,5 96,4 100,3"/>
|
|
842
|
+
</svg>
|
|
843
|
+
</div>
|
|
844
|
+
</div>
|
|
845
|
+
|
|
846
|
+
<!-- 成功率 -->
|
|
847
|
+
<div class="metric-card">
|
|
848
|
+
<div class="metric-card-head">
|
|
849
|
+
<span class="metric-card-title">成功率</span>
|
|
850
|
+
<svg class="metric-card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="M22 4L12 14.01l-3-3"/></svg>
|
|
851
|
+
</div>
|
|
852
|
+
<div class="metric-card-body">
|
|
853
|
+
<span class="metric-card-value">99.2%</span>
|
|
854
|
+
<span class="metric-card-trend up">
|
|
855
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
|
|
856
|
+
0.3%
|
|
857
|
+
</span>
|
|
858
|
+
</div>
|
|
859
|
+
<div class="metric-card-spark">
|
|
860
|
+
<svg viewBox="0 0 100 24" preserveAspectRatio="none">
|
|
861
|
+
<polyline class="chart-line chart-line-success" points="0,8 8,9 16,7 24,8 32,6 40,7 48,5 56,6 64,4 72,5 80,3 88,4 96,3 100,2"/>
|
|
862
|
+
</svg>
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
|
|
866
|
+
<!-- 平均延迟 -->
|
|
867
|
+
<div class="metric-card">
|
|
868
|
+
<div class="metric-card-head">
|
|
869
|
+
<span class="metric-card-title">平均延迟</span>
|
|
870
|
+
<svg class="metric-card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
|
871
|
+
</div>
|
|
872
|
+
<div class="metric-card-body">
|
|
873
|
+
<span class="metric-card-value">128<span style="font-size:14px;color:hsl(var(--muted-foreground));font-weight:var(--font-weight-normal);margin-left:2px;">ms</span></span>
|
|
874
|
+
<span class="metric-card-trend down-good">
|
|
875
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M19 12l-7 7-7-7"/></svg>
|
|
876
|
+
12.1%
|
|
877
|
+
</span>
|
|
878
|
+
</div>
|
|
879
|
+
<div class="metric-card-spark">
|
|
880
|
+
<svg viewBox="0 0 100 24" preserveAspectRatio="none">
|
|
881
|
+
<polyline class="chart-line chart-line-success" points="0,5 8,7 16,6 24,9 32,8 40,11 48,10 56,13 64,12 72,15 80,14 88,17 96,16 100,18"/>
|
|
882
|
+
</svg>
|
|
883
|
+
</div>
|
|
884
|
+
</div>
|
|
885
|
+
|
|
886
|
+
<!-- Token 消耗 -->
|
|
887
|
+
<div class="metric-card">
|
|
888
|
+
<div class="metric-card-head">
|
|
889
|
+
<span class="metric-card-title">Token 消耗</span>
|
|
890
|
+
<svg class="metric-card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 12h8M12 8v8"/></svg>
|
|
891
|
+
</div>
|
|
892
|
+
<div class="metric-card-body">
|
|
893
|
+
<span class="metric-card-value">1.2<span style="font-size:14px;color:hsl(var(--muted-foreground));font-weight:var(--font-weight-normal);margin-left:2px;">M</span></span>
|
|
894
|
+
<span class="metric-card-trend up">
|
|
895
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
|
|
896
|
+
8.7%
|
|
897
|
+
</span>
|
|
898
|
+
</div>
|
|
899
|
+
<div class="metric-card-spark">
|
|
900
|
+
<svg viewBox="0 0 100 24" preserveAspectRatio="none">
|
|
901
|
+
<polyline class="chart-line" points="0,16 8,15 16,17 24,13 32,14 40,12 48,10 56,11 64,8 72,9 80,6 88,7 96,5 100,4"/>
|
|
902
|
+
</svg>
|
|
903
|
+
</div>
|
|
904
|
+
</div>
|
|
905
|
+
|
|
906
|
+
</div>
|
|
907
|
+
|
|
908
|
+
<!-- ============ ChartPanel — 全宽请求趋势 ============ -->
|
|
909
|
+
<div class="chart-panel chart-panel-full">
|
|
910
|
+
<div class="chart-panel-head">
|
|
911
|
+
<span class="chart-panel-title">请求趋势</span>
|
|
912
|
+
<div class="chart-panel-actions">
|
|
913
|
+
<button class="chart-panel-link">
|
|
914
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
|
|
915
|
+
导出
|
|
916
|
+
</button>
|
|
917
|
+
</div>
|
|
918
|
+
</div>
|
|
919
|
+
<div class="chart-panel-body">
|
|
920
|
+
<!-- 主请求趋势折线图:横向时间 00:00~23:00,纵向请求数 -->
|
|
921
|
+
<svg viewBox="0 0 800 220" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
|
922
|
+
<!-- 网格线(横向 5 条) -->
|
|
923
|
+
<line class="chart-grid-line" x1="40" y1="20" x2="780" y2="20"/>
|
|
924
|
+
<line class="chart-grid-line" x1="40" y1="60" x2="780" y2="60"/>
|
|
925
|
+
<line class="chart-grid-line" x1="40" y1="100" x2="780" y2="100"/>
|
|
926
|
+
<line class="chart-grid-line" x1="40" y1="140" x2="780" y2="140"/>
|
|
927
|
+
<line class="chart-grid-line" x1="40" y1="180" x2="780" y2="180"/>
|
|
928
|
+
|
|
929
|
+
<!-- Y 轴标签 -->
|
|
930
|
+
<text class="chart-axis-label" x="32" y="24" text-anchor="end">800</text>
|
|
931
|
+
<text class="chart-axis-label" x="32" y="64" text-anchor="end">600</text>
|
|
932
|
+
<text class="chart-axis-label" x="32" y="104" text-anchor="end">400</text>
|
|
933
|
+
<text class="chart-axis-label" x="32" y="144" text-anchor="end">200</text>
|
|
934
|
+
<text class="chart-axis-label" x="32" y="184" text-anchor="end">0</text>
|
|
935
|
+
|
|
936
|
+
<!-- X 轴标签(每 4 小时) -->
|
|
937
|
+
<text class="chart-axis-label" x="40" y="200" text-anchor="middle">00:00</text>
|
|
938
|
+
<text class="chart-axis-label" x="163" y="200" text-anchor="middle">04:00</text>
|
|
939
|
+
<text class="chart-axis-label" x="287" y="200" text-anchor="middle">08:00</text>
|
|
940
|
+
<text class="chart-axis-label" x="410" y="200" text-anchor="middle">12:00</text>
|
|
941
|
+
<text class="chart-axis-label" x="533" y="200" text-anchor="middle">16:00</text>
|
|
942
|
+
<text class="chart-axis-label" x="657" y="200" text-anchor="middle">20:00</text>
|
|
943
|
+
<text class="chart-axis-label" x="780" y="200" text-anchor="middle">23:00</text>
|
|
944
|
+
|
|
945
|
+
<!-- 面积填充 -->
|
|
946
|
+
<path class="chart-area-fill" d="M40,170 L70,165 L100,160 L130,158 L160,150 L190,140 L220,130 L250,115 L280,105 L310,90 L340,80 L370,70 L400,55 L430,60 L460,75 L490,85 L520,95 L550,80 L580,70 L610,60 L640,55 L670,50 L700,55 L730,65 L760,80 L780,90 L780,180 L40,180 Z"/>
|
|
947
|
+
|
|
948
|
+
<!-- 折线 -->
|
|
949
|
+
<polyline class="chart-line" points="40,170 70,165 100,160 130,158 160,150 190,140 220,130 250,115 280,105 310,90 340,80 370,70 400,55 430,60 460,75 490,85 520,95 550,80 580,70 610,60 640,55 670,50 700,55 730,65 760,80 780,90"/>
|
|
950
|
+
|
|
951
|
+
<!-- 数据点(高亮关键点) -->
|
|
952
|
+
<circle cx="400" cy="55" r="3.5" fill="hsl(var(--primary))"/>
|
|
953
|
+
<circle cx="670" cy="50" r="3.5" fill="hsl(var(--primary))"/>
|
|
954
|
+
</svg>
|
|
955
|
+
</div>
|
|
956
|
+
</div>
|
|
957
|
+
|
|
958
|
+
<!-- ============ Tabs ============ -->
|
|
959
|
+
<div class="tabs">
|
|
960
|
+
<ul class="tabs-list">
|
|
961
|
+
<li class="tab-item active" onclick="switchTab(0)">性能指标</li>
|
|
962
|
+
<li class="tab-item" onclick="switchTab(1)">告警记录</li>
|
|
963
|
+
<li class="tab-item" onclick="switchTab(2)">调用明细</li>
|
|
964
|
+
</ul>
|
|
965
|
+
</div>
|
|
966
|
+
|
|
967
|
+
<!-- ===== Tab 0: 性能指标(2x2 ChartPanel Grid) ===== -->
|
|
968
|
+
<div class="tab-content" id="tab-0">
|
|
969
|
+
<div class="chart-grid-2">
|
|
970
|
+
|
|
971
|
+
<!-- 延迟分布(柱状图) -->
|
|
972
|
+
<div class="chart-panel chart-panel-grid-cell" style="margin-bottom:0;">
|
|
973
|
+
<div class="chart-panel-head">
|
|
974
|
+
<span class="chart-panel-title">延迟分布</span>
|
|
975
|
+
<div class="chart-panel-actions">
|
|
976
|
+
<button class="chart-panel-link">查看详情</button>
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
979
|
+
<div class="chart-panel-body">
|
|
980
|
+
<svg viewBox="0 0 600 180" preserveAspectRatio="none">
|
|
981
|
+
<!-- 网格线 -->
|
|
982
|
+
<line class="chart-grid-line" x1="40" y1="20" x2="580" y2="20"/>
|
|
983
|
+
<line class="chart-grid-line" x1="40" y1="60" x2="580" y2="60"/>
|
|
984
|
+
<line class="chart-grid-line" x1="40" y1="100" x2="580" y2="100"/>
|
|
985
|
+
<line class="chart-grid-line" x1="40" y1="140" x2="580" y2="140"/>
|
|
986
|
+
|
|
987
|
+
<text class="chart-axis-label" x="32" y="24" text-anchor="end">2k</text>
|
|
988
|
+
<text class="chart-axis-label" x="32" y="64" text-anchor="end">1.5k</text>
|
|
989
|
+
<text class="chart-axis-label" x="32" y="104" text-anchor="end">1k</text>
|
|
990
|
+
<text class="chart-axis-label" x="32" y="144" text-anchor="end">500</text>
|
|
991
|
+
|
|
992
|
+
<!-- 柱体 -->
|
|
993
|
+
<rect class="chart-bar" x="60" y="120" width="42" height="20"/>
|
|
994
|
+
<rect class="chart-bar" x="118" y="80" width="42" height="60"/>
|
|
995
|
+
<rect class="chart-bar" x="176" y="40" width="42" height="100"/>
|
|
996
|
+
<rect class="chart-bar" x="234" y="55" width="42" height="85"/>
|
|
997
|
+
<rect class="chart-bar" x="292" y="75" width="42" height="65"/>
|
|
998
|
+
<rect class="chart-bar-alt" x="350" y="100" width="42" height="40"/>
|
|
999
|
+
<rect class="chart-bar-alt" x="408" y="115" width="42" height="25"/>
|
|
1000
|
+
<rect class="chart-bar-alt" x="466" y="125" width="42" height="15"/>
|
|
1001
|
+
<rect class="chart-bar-alt" x="524" y="132" width="42" height="8"/>
|
|
1002
|
+
|
|
1003
|
+
<!-- X 轴标签 -->
|
|
1004
|
+
<text class="chart-axis-label" x="81" y="160" text-anchor="middle">0-50</text>
|
|
1005
|
+
<text class="chart-axis-label" x="139" y="160" text-anchor="middle">50-100</text>
|
|
1006
|
+
<text class="chart-axis-label" x="197" y="160" text-anchor="middle">100-200</text>
|
|
1007
|
+
<text class="chart-axis-label" x="255" y="160" text-anchor="middle">200-300</text>
|
|
1008
|
+
<text class="chart-axis-label" x="313" y="160" text-anchor="middle">300-500</text>
|
|
1009
|
+
<text class="chart-axis-label" x="371" y="160" text-anchor="middle">500-800</text>
|
|
1010
|
+
<text class="chart-axis-label" x="429" y="160" text-anchor="middle">800-1.2k</text>
|
|
1011
|
+
<text class="chart-axis-label" x="487" y="160" text-anchor="middle">1.2k-2k</text>
|
|
1012
|
+
<text class="chart-axis-label" x="545" y="160" text-anchor="middle">2k+</text>
|
|
1013
|
+
</svg>
|
|
1014
|
+
</div>
|
|
1015
|
+
</div>
|
|
1016
|
+
|
|
1017
|
+
<!-- 吞吐量(折线图) -->
|
|
1018
|
+
<div class="chart-panel chart-panel-grid-cell" style="margin-bottom:0;">
|
|
1019
|
+
<div class="chart-panel-head">
|
|
1020
|
+
<span class="chart-panel-title">吞吐量 (QPS)</span>
|
|
1021
|
+
<div class="chart-panel-actions">
|
|
1022
|
+
<button class="chart-panel-link">查看详情</button>
|
|
1023
|
+
</div>
|
|
1024
|
+
</div>
|
|
1025
|
+
<div class="chart-panel-body">
|
|
1026
|
+
<svg viewBox="0 0 600 180" preserveAspectRatio="none">
|
|
1027
|
+
<line class="chart-grid-line" x1="40" y1="20" x2="580" y2="20"/>
|
|
1028
|
+
<line class="chart-grid-line" x1="40" y1="60" x2="580" y2="60"/>
|
|
1029
|
+
<line class="chart-grid-line" x1="40" y1="100" x2="580" y2="100"/>
|
|
1030
|
+
<line class="chart-grid-line" x1="40" y1="140" x2="580" y2="140"/>
|
|
1031
|
+
|
|
1032
|
+
<text class="chart-axis-label" x="32" y="24" text-anchor="end">200</text>
|
|
1033
|
+
<text class="chart-axis-label" x="32" y="64" text-anchor="end">150</text>
|
|
1034
|
+
<text class="chart-axis-label" x="32" y="104" text-anchor="end">100</text>
|
|
1035
|
+
<text class="chart-axis-label" x="32" y="144" text-anchor="end">50</text>
|
|
1036
|
+
|
|
1037
|
+
<polyline class="chart-line" points="40,130 80,125 120,118 160,112 200,95 240,85 280,72 320,60 360,55 400,68 440,80 480,72 520,60 560,52 580,48"/>
|
|
1038
|
+
|
|
1039
|
+
<text class="chart-axis-label" x="40" y="160" text-anchor="middle">00:00</text>
|
|
1040
|
+
<text class="chart-axis-label" x="175" y="160" text-anchor="middle">06:00</text>
|
|
1041
|
+
<text class="chart-axis-label" x="310" y="160" text-anchor="middle">12:00</text>
|
|
1042
|
+
<text class="chart-axis-label" x="445" y="160" text-anchor="middle">18:00</text>
|
|
1043
|
+
<text class="chart-axis-label" x="580" y="160" text-anchor="middle">23:00</text>
|
|
1044
|
+
</svg>
|
|
1045
|
+
</div>
|
|
1046
|
+
</div>
|
|
1047
|
+
|
|
1048
|
+
<!-- 错误率(面积图) -->
|
|
1049
|
+
<div class="chart-panel chart-panel-grid-cell" style="margin-bottom:0;">
|
|
1050
|
+
<div class="chart-panel-head">
|
|
1051
|
+
<span class="chart-panel-title">错误率</span>
|
|
1052
|
+
<div class="chart-panel-actions">
|
|
1053
|
+
<button class="chart-panel-link">查看详情</button>
|
|
1054
|
+
</div>
|
|
1055
|
+
</div>
|
|
1056
|
+
<div class="chart-panel-body">
|
|
1057
|
+
<svg viewBox="0 0 600 180" preserveAspectRatio="none">
|
|
1058
|
+
<line class="chart-grid-line" x1="40" y1="20" x2="580" y2="20"/>
|
|
1059
|
+
<line class="chart-grid-line" x1="40" y1="60" x2="580" y2="60"/>
|
|
1060
|
+
<line class="chart-grid-line" x1="40" y1="100" x2="580" y2="100"/>
|
|
1061
|
+
<line class="chart-grid-line" x1="40" y1="140" x2="580" y2="140"/>
|
|
1062
|
+
|
|
1063
|
+
<text class="chart-axis-label" x="32" y="24" text-anchor="end">10%</text>
|
|
1064
|
+
<text class="chart-axis-label" x="32" y="64" text-anchor="end">7.5%</text>
|
|
1065
|
+
<text class="chart-axis-label" x="32" y="104" text-anchor="end">5%</text>
|
|
1066
|
+
<text class="chart-axis-label" x="32" y="144" text-anchor="end">2.5%</text>
|
|
1067
|
+
|
|
1068
|
+
<!-- 阈值线 5% -->
|
|
1069
|
+
<line x1="40" y1="100" x2="580" y2="100" stroke="hsl(var(--destructive))" stroke-width="0.8" stroke-dasharray="4 4" opacity="0.6"/>
|
|
1070
|
+
|
|
1071
|
+
<!-- 面积填充(红色) -->
|
|
1072
|
+
<path d="M40,150 L80,148 L120,145 L160,142 L200,140 L240,135 L280,128 L320,115 L360,90 L400,95 L440,110 L480,125 L520,135 L560,142 L580,145 L580,160 L40,160 Z" fill="hsl(var(--destructive) / 0.1)"/>
|
|
1073
|
+
<polyline class="chart-line chart-line-destructive" points="40,150 80,148 120,145 160,142 200,140 240,135 280,128 320,115 360,90 400,95 440,110 480,125 520,135 560,142 580,145"/>
|
|
1074
|
+
|
|
1075
|
+
<!-- 异常点(错误率超阈值) -->
|
|
1076
|
+
<circle cx="360" cy="90" r="4" fill="hsl(var(--destructive))"/>
|
|
1077
|
+
|
|
1078
|
+
<text class="chart-axis-label" x="40" y="160" text-anchor="middle">00:00</text>
|
|
1079
|
+
<text class="chart-axis-label" x="175" y="160" text-anchor="middle">06:00</text>
|
|
1080
|
+
<text class="chart-axis-label" x="310" y="160" text-anchor="middle">12:00</text>
|
|
1081
|
+
<text class="chart-axis-label" x="445" y="160" text-anchor="middle">18:00</text>
|
|
1082
|
+
<text class="chart-axis-label" x="580" y="160" text-anchor="middle">23:00</text>
|
|
1083
|
+
</svg>
|
|
1084
|
+
</div>
|
|
1085
|
+
</div>
|
|
1086
|
+
|
|
1087
|
+
<!-- Token 消耗趋势(折线图) -->
|
|
1088
|
+
<div class="chart-panel chart-panel-grid-cell" style="margin-bottom:0;">
|
|
1089
|
+
<div class="chart-panel-head">
|
|
1090
|
+
<span class="chart-panel-title">Token 消耗趋势</span>
|
|
1091
|
+
<div class="chart-panel-actions">
|
|
1092
|
+
<button class="chart-panel-link">查看详情</button>
|
|
1093
|
+
</div>
|
|
1094
|
+
</div>
|
|
1095
|
+
<div class="chart-panel-body">
|
|
1096
|
+
<svg viewBox="0 0 600 180" preserveAspectRatio="none">
|
|
1097
|
+
<line class="chart-grid-line" x1="40" y1="20" x2="580" y2="20"/>
|
|
1098
|
+
<line class="chart-grid-line" x1="40" y1="60" x2="580" y2="60"/>
|
|
1099
|
+
<line class="chart-grid-line" x1="40" y1="100" x2="580" y2="100"/>
|
|
1100
|
+
<line class="chart-grid-line" x1="40" y1="140" x2="580" y2="140"/>
|
|
1101
|
+
|
|
1102
|
+
<text class="chart-axis-label" x="32" y="24" text-anchor="end">100k</text>
|
|
1103
|
+
<text class="chart-axis-label" x="32" y="64" text-anchor="end">75k</text>
|
|
1104
|
+
<text class="chart-axis-label" x="32" y="104" text-anchor="end">50k</text>
|
|
1105
|
+
<text class="chart-axis-label" x="32" y="144" text-anchor="end">25k</text>
|
|
1106
|
+
|
|
1107
|
+
<!-- 输入 Token -->
|
|
1108
|
+
<polyline class="chart-line" points="40,140 80,135 120,128 160,120 200,108 240,98 280,88 320,75 360,65 400,72 440,80 480,68 520,55 560,45 580,42"/>
|
|
1109
|
+
<!-- 输出 Token (warning 色) -->
|
|
1110
|
+
<polyline class="chart-line chart-line-warning" points="40,150 80,148 120,142 160,135 200,128 240,120 280,112 320,100 360,92 400,98 440,105 480,95 520,85 560,75 580,72"/>
|
|
1111
|
+
|
|
1112
|
+
<text class="chart-axis-label" x="40" y="160" text-anchor="middle">00:00</text>
|
|
1113
|
+
<text class="chart-axis-label" x="175" y="160" text-anchor="middle">06:00</text>
|
|
1114
|
+
<text class="chart-axis-label" x="310" y="160" text-anchor="middle">12:00</text>
|
|
1115
|
+
<text class="chart-axis-label" x="445" y="160" text-anchor="middle">18:00</text>
|
|
1116
|
+
<text class="chart-axis-label" x="580" y="160" text-anchor="middle">23:00</text>
|
|
1117
|
+
|
|
1118
|
+
<!-- 图例 -->
|
|
1119
|
+
<g transform="translate(420,15)">
|
|
1120
|
+
<line x1="0" y1="6" x2="14" y2="6" stroke="hsl(var(--primary))" stroke-width="2"/>
|
|
1121
|
+
<text class="chart-axis-label" x="18" y="9">输入</text>
|
|
1122
|
+
<line x1="60" y1="6" x2="74" y2="6" stroke="hsl(var(--warning))" stroke-width="2"/>
|
|
1123
|
+
<text class="chart-axis-label" x="78" y="9">输出</text>
|
|
1124
|
+
</g>
|
|
1125
|
+
</svg>
|
|
1126
|
+
</div>
|
|
1127
|
+
</div>
|
|
1128
|
+
|
|
1129
|
+
</div>
|
|
1130
|
+
</div>
|
|
1131
|
+
|
|
1132
|
+
<!-- ===== Tab 1: 告警记录(AlertList) ===== -->
|
|
1133
|
+
<div class="tab-content hidden" id="tab-1">
|
|
1134
|
+
<div class="alert-list" style="border:1px solid hsl(var(--gray-line)); border-radius: var(--radius-md); overflow:hidden;">
|
|
1135
|
+
|
|
1136
|
+
<div class="alert-item">
|
|
1137
|
+
<span class="alert-dot critical"></span>
|
|
1138
|
+
<div class="alert-content">
|
|
1139
|
+
<div class="alert-head">
|
|
1140
|
+
<span class="alert-severity critical">严重</span>
|
|
1141
|
+
<span class="alert-title">错误率超过阈值 (5%)</span>
|
|
1142
|
+
<span class="alert-time">2026-05-27 14:23</span>
|
|
1143
|
+
</div>
|
|
1144
|
+
<div class="alert-desc">错误率达到 5.2%, 超过告警阈值 5%。受影响模型: gpt-4o, claude-4。建议检查上游服务连通性与限流配置。</div>
|
|
1145
|
+
</div>
|
|
1146
|
+
</div>
|
|
1147
|
+
|
|
1148
|
+
<div class="alert-item">
|
|
1149
|
+
<span class="alert-dot warning"></span>
|
|
1150
|
+
<div class="alert-content">
|
|
1151
|
+
<div class="alert-head">
|
|
1152
|
+
<span class="alert-severity warning">警告</span>
|
|
1153
|
+
<span class="alert-title">平均延迟升高</span>
|
|
1154
|
+
<span class="alert-time">2026-05-27 11:05</span>
|
|
1155
|
+
</div>
|
|
1156
|
+
<div class="alert-desc">P99 延迟达到 2.3s, 超过预期 2s。建议关注模型 deepseek-v3 的响应时间趋势。</div>
|
|
1157
|
+
</div>
|
|
1158
|
+
</div>
|
|
1159
|
+
|
|
1160
|
+
<div class="alert-item">
|
|
1161
|
+
<span class="alert-dot info"></span>
|
|
1162
|
+
<div class="alert-content">
|
|
1163
|
+
<div class="alert-head">
|
|
1164
|
+
<span class="alert-severity info">提示</span>
|
|
1165
|
+
<span class="alert-title">Token 消耗接近配额</span>
|
|
1166
|
+
<span class="alert-time">2026-05-27 09:30</span>
|
|
1167
|
+
</div>
|
|
1168
|
+
<div class="alert-desc">本月 Token 使用已达配额 85%。建议提前评估是否需要扩容或调整限速策略。</div>
|
|
1169
|
+
</div>
|
|
1170
|
+
</div>
|
|
1171
|
+
|
|
1172
|
+
<div class="alert-item">
|
|
1173
|
+
<span class="alert-dot info"></span>
|
|
1174
|
+
<div class="alert-content">
|
|
1175
|
+
<div class="alert-head">
|
|
1176
|
+
<span class="alert-severity info">提示</span>
|
|
1177
|
+
<span class="alert-title">新增消费者接入</span>
|
|
1178
|
+
<span class="alert-time">2026-05-27 08:12</span>
|
|
1179
|
+
</div>
|
|
1180
|
+
<div class="alert-desc">consumer-D 已成功接入并完成首次调用,当前实例消费者总数: 12。</div>
|
|
1181
|
+
</div>
|
|
1182
|
+
</div>
|
|
1183
|
+
|
|
1184
|
+
</div>
|
|
1185
|
+
|
|
1186
|
+
<!-- 空态参考(注释保留) -->
|
|
1187
|
+
<!--
|
|
1188
|
+
<div class="alert-empty">
|
|
1189
|
+
<div class="alert-empty-icon">
|
|
1190
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>
|
|
1191
|
+
</div>
|
|
1192
|
+
<div>暂无告警,系统运行正常</div>
|
|
1193
|
+
</div>
|
|
1194
|
+
-->
|
|
1195
|
+
</div>
|
|
1196
|
+
|
|
1197
|
+
<!-- ===== Tab 2: 调用明细(DataTable) ===== -->
|
|
1198
|
+
<div class="tab-content hidden" id="tab-2">
|
|
1199
|
+
<table class="data-table">
|
|
1200
|
+
<thead>
|
|
1201
|
+
<tr>
|
|
1202
|
+
<th style="width:180px;">时间</th>
|
|
1203
|
+
<th style="width:140px;">模型</th>
|
|
1204
|
+
<th style="width:140px;">消费者</th>
|
|
1205
|
+
<th style="width:100px;">状态</th>
|
|
1206
|
+
<th style="width:120px;">延迟</th>
|
|
1207
|
+
<th>Token</th>
|
|
1208
|
+
</tr>
|
|
1209
|
+
</thead>
|
|
1210
|
+
<tbody>
|
|
1211
|
+
<tr>
|
|
1212
|
+
<td class="muted numeric">2026-05-27 15:42:18</td>
|
|
1213
|
+
<td>gpt-4o</td>
|
|
1214
|
+
<td>consumer-A</td>
|
|
1215
|
+
<td><span class="badge badge-success"><span class="badge-dot"></span>成功</span></td>
|
|
1216
|
+
<td class="numeric">856 ms</td>
|
|
1217
|
+
<td class="numeric">1,234</td>
|
|
1218
|
+
</tr>
|
|
1219
|
+
<tr>
|
|
1220
|
+
<td class="muted numeric">2026-05-27 15:42:15</td>
|
|
1221
|
+
<td>deepseek-v3</td>
|
|
1222
|
+
<td>consumer-B</td>
|
|
1223
|
+
<td><span class="badge badge-success"><span class="badge-dot"></span>成功</span></td>
|
|
1224
|
+
<td class="numeric">342 ms</td>
|
|
1225
|
+
<td class="numeric">567</td>
|
|
1226
|
+
</tr>
|
|
1227
|
+
<tr>
|
|
1228
|
+
<td class="muted numeric">2026-05-27 15:42:10</td>
|
|
1229
|
+
<td>gpt-4o</td>
|
|
1230
|
+
<td>consumer-A</td>
|
|
1231
|
+
<td><span class="badge badge-destructive"><span class="badge-dot"></span>失败</span></td>
|
|
1232
|
+
<td class="numeric">60,000 ms</td>
|
|
1233
|
+
<td class="numeric muted">0</td>
|
|
1234
|
+
</tr>
|
|
1235
|
+
<tr>
|
|
1236
|
+
<td class="muted numeric">2026-05-27 15:42:05</td>
|
|
1237
|
+
<td>claude-4</td>
|
|
1238
|
+
<td>consumer-C</td>
|
|
1239
|
+
<td><span class="badge badge-success"><span class="badge-dot"></span>成功</span></td>
|
|
1240
|
+
<td class="numeric">1,205 ms</td>
|
|
1241
|
+
<td class="numeric">2,341</td>
|
|
1242
|
+
</tr>
|
|
1243
|
+
<tr>
|
|
1244
|
+
<td class="muted numeric">2026-05-27 15:41:58</td>
|
|
1245
|
+
<td>gpt-4o</td>
|
|
1246
|
+
<td>consumer-A</td>
|
|
1247
|
+
<td><span class="badge badge-success"><span class="badge-dot"></span>成功</span></td>
|
|
1248
|
+
<td class="numeric">923 ms</td>
|
|
1249
|
+
<td class="numeric">1,876</td>
|
|
1250
|
+
</tr>
|
|
1251
|
+
<tr>
|
|
1252
|
+
<td class="muted numeric">2026-05-27 15:41:42</td>
|
|
1253
|
+
<td>deepseek-v3</td>
|
|
1254
|
+
<td>consumer-B</td>
|
|
1255
|
+
<td><span class="badge badge-success"><span class="badge-dot"></span>成功</span></td>
|
|
1256
|
+
<td class="numeric">298 ms</td>
|
|
1257
|
+
<td class="numeric">412</td>
|
|
1258
|
+
</tr>
|
|
1259
|
+
<tr>
|
|
1260
|
+
<td class="muted numeric">2026-05-27 15:41:30</td>
|
|
1261
|
+
<td>claude-4</td>
|
|
1262
|
+
<td>consumer-C</td>
|
|
1263
|
+
<td><span class="badge badge-success"><span class="badge-dot"></span>成功</span></td>
|
|
1264
|
+
<td class="numeric">1,402 ms</td>
|
|
1265
|
+
<td class="numeric">3,108</td>
|
|
1266
|
+
</tr>
|
|
1267
|
+
<tr>
|
|
1268
|
+
<td class="muted numeric">2026-05-27 15:41:18</td>
|
|
1269
|
+
<td>gpt-4o</td>
|
|
1270
|
+
<td>consumer-D</td>
|
|
1271
|
+
<td><span class="badge badge-success"><span class="badge-dot"></span>成功</span></td>
|
|
1272
|
+
<td class="numeric">762 ms</td>
|
|
1273
|
+
<td class="numeric">1,022</td>
|
|
1274
|
+
</tr>
|
|
1275
|
+
</tbody>
|
|
1276
|
+
</table>
|
|
1277
|
+
</div>
|
|
1278
|
+
|
|
1279
|
+
</div><!-- /card -->
|
|
1280
|
+
</div><!-- /page-container -->
|
|
1281
|
+
</main>
|
|
1282
|
+
|
|
1283
|
+
<script>
|
|
1284
|
+
// ===== Sidebar =====
|
|
1285
|
+
const sidebar = document.querySelector('.sidebar');
|
|
1286
|
+
document.querySelector('.sidebar-collapse-btn').addEventListener('click', function() {
|
|
1287
|
+
sidebar.classList.toggle('collapsed');
|
|
1288
|
+
});
|
|
1289
|
+
// Sidebar expand via logo click (collapsed state)
|
|
1290
|
+
document.getElementById('sidebarLogoArea').addEventListener('click', function() {
|
|
1291
|
+
if (sidebar.classList.contains('collapsed')) {
|
|
1292
|
+
sidebar.classList.remove('collapsed');
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
// Sidebar sub-menu toggle
|
|
1296
|
+
document.querySelectorAll('.sidebar-menu-item[data-toggle]').forEach(item => {
|
|
1297
|
+
item.addEventListener('click', function(e) {
|
|
1298
|
+
e.preventDefault();
|
|
1299
|
+
this.classList.toggle('expanded');
|
|
1300
|
+
const targetId = this.getAttribute('data-toggle');
|
|
1301
|
+
const target = document.getElementById(targetId);
|
|
1302
|
+
if (target) target.classList.toggle('show');
|
|
1303
|
+
});
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// ===== TimeRangeSelector =====
|
|
1307
|
+
function selectTimeRange(el) {
|
|
1308
|
+
document.querySelectorAll('.time-range-item').forEach(t => t.classList.remove('active'));
|
|
1309
|
+
el.classList.add('active');
|
|
1310
|
+
// 实际场景下这里会触发图表数据重拉
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// ===== Refresh =====
|
|
1314
|
+
function refresh() {
|
|
1315
|
+
// 轻量旋转动效
|
|
1316
|
+
const btn = event.currentTarget;
|
|
1317
|
+
const icon = btn.querySelector('svg');
|
|
1318
|
+
if (icon) {
|
|
1319
|
+
icon.style.transition = 'transform 0.6s ease';
|
|
1320
|
+
icon.style.transform = 'rotate(360deg)';
|
|
1321
|
+
setTimeout(() => {
|
|
1322
|
+
icon.style.transition = 'none';
|
|
1323
|
+
icon.style.transform = 'rotate(0deg)';
|
|
1324
|
+
}, 600);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// ===== Tabs =====
|
|
1329
|
+
function switchTab(index) {
|
|
1330
|
+
document.querySelectorAll('.tab-item').forEach((t, i) => {
|
|
1331
|
+
t.classList.toggle('active', i === index);
|
|
1332
|
+
});
|
|
1333
|
+
document.querySelectorAll('.tab-content').forEach((c, i) => {
|
|
1334
|
+
c.classList.toggle('hidden', i !== index);
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
</script>
|
|
1338
|
+
</body>
|
|
1339
|
+
</html>
|