@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.
Files changed (75) hide show
  1. package/manifest.json +23 -37
  2. package/package.json +2 -2
  3. package/src/teamix-evo-design-opentrek/SKILL.md +210 -42
  4. package/src/teamix-evo-design-opentrek/boundaries.md +3 -3
  5. package/src/teamix-evo-design-opentrek/components.md +41 -40
  6. package/src/teamix-evo-design-opentrek/examples/detail-ai-gateway-1.html +1069 -0
  7. package/src/teamix-evo-design-opentrek/examples/detail-ai-gateway-instance.html +941 -0
  8. package/src/teamix-evo-design-opentrek/examples/detail-page-api-doc.html +906 -0
  9. package/src/teamix-evo-design-opentrek/examples/detail-page-config.html +993 -0
  10. package/src/teamix-evo-design-opentrek/examples/detail-page-monitor.html +1339 -0
  11. package/src/teamix-evo-design-opentrek/examples/detail-page.html +933 -0
  12. package/src/teamix-evo-design-opentrek/examples/settings-page.html +1119 -0
  13. package/src/teamix-evo-design-opentrek/examples/standard-card-list.html +1094 -0
  14. package/src/teamix-evo-design-opentrek/examples/standard-table-list.html +1361 -0
  15. package/src/teamix-evo-design-opentrek/examples/wizard-form-page.html +877 -0
  16. package/src/teamix-evo-design-opentrek/flows.md +85 -12
  17. package/src/teamix-evo-design-opentrek/foundations.md +9 -9
  18. package/src/teamix-evo-design-opentrek/generation-flow.md +15 -3
  19. package/src/teamix-evo-design-opentrek/pages/detail-page/SKILL.md +260 -0
  20. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/api-doc-detail.md +163 -0
  21. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/comparison-detail.md +100 -0
  22. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/monitor-detail.md +190 -0
  23. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/resource-detail.md +148 -0
  24. package/src/teamix-evo-design-opentrek/pages/form-page/SKILL.md +362 -0
  25. package/src/teamix-evo-design-opentrek/pages/list-page/SKILL.md +286 -0
  26. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/action-column-spec.md +60 -0
  27. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/column-meta-rules.md +117 -0
  28. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/search-combo-spec.md +194 -0
  29. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/state-action-pattern.md +51 -0
  30. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/advanced-filter-list.md +94 -0
  31. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/card-list.md +558 -0
  32. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/drawer-list.md +76 -0
  33. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/expandable-list.md +70 -0
  34. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/l2-sidebar-list.md +73 -0
  35. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/standard-list.md +198 -0
  36. package/src/teamix-evo-design-opentrek/patterns/color-mapping.md +1 -1
  37. package/src/teamix-evo-design-opentrek/patterns/detail-page.md +217 -152
  38. package/src/teamix-evo-design-opentrek/patterns/form-page.md +437 -231
  39. package/src/teamix-evo-design-opentrek/patterns/list-page.md +220 -292
  40. package/src/teamix-evo-design-opentrek/patterns/page-types.md +40 -130
  41. package/src/teamix-evo-design-opentrek/rules/_assets/OP_AGENT RUNTIME.svg +1 -0
  42. package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI GATEWAY.svg +1 -0
  43. package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI STUDIO.svg +1 -0
  44. package/src/teamix-evo-design-opentrek/rules/_assets/OP_DEV-2.svg +1 -0
  45. package/src/teamix-evo-design-opentrek/rules/_assets/OP_LOGO.svg +1 -0
  46. package/src/teamix-evo-design-opentrek/rules/_assets/OP_OPS.svg +1 -0
  47. package/src/teamix-evo-design-opentrek/rules/boundaries.rules.json +3 -3
  48. package/src/teamix-evo-design-opentrek/rules/business-mapping.json +124 -0
  49. package/src/teamix-evo-design-opentrek/rules/common-components.json +924 -0
  50. package/src/teamix-evo-design-opentrek/rules/component-specs.json +1083 -0
  51. package/src/teamix-evo-design-opentrek/rules/design-tokens.css +433 -0
  52. package/src/teamix-evo-design-opentrek/rules/design-tokens.json +2798 -0
  53. package/src/teamix-evo-design-opentrek/rules/layout-rules.json +218 -0
  54. package/src/teamix-evo-design-opentrek/rules/page-flow.json +351 -0
  55. package/src/teamix-evo-design-opentrek/rules/page-frame.json +241 -0
  56. package/src/teamix-evo-design-opentrek/rules/page-header-spec.md +123 -0
  57. package/src/teamix-evo-design-opentrek/rules/page-types.json +206 -0
  58. package/src/teamix-evo-design-opentrek/rules/sidebar-spec.md +217 -0
  59. package/src/teamix-evo-design-opentrek/rules/styling.json +188 -0
  60. package/src/teamix-evo-design-opentrek/rules/token-mapping.md +284 -0
  61. package/src/teamix-evo-design-uni-manager/SKILL.md +1 -1
  62. package/src/teamix-evo-design-uni-manager/boundaries.md +3 -3
  63. package/src/teamix-evo-design-uni-manager/brand.md +1 -1
  64. package/src/teamix-evo-design-uni-manager/components.md +30 -28
  65. package/src/teamix-evo-design-uni-manager/foundations.md +21 -21
  66. package/src/teamix-evo-design-uni-manager/generation-flow.md +3 -1
  67. package/src/teamix-evo-design-uni-manager/patterns/detail-page.md +1 -1
  68. package/src/teamix-evo-design-uni-manager/patterns/form-page.md +18 -18
  69. package/src/teamix-evo-design-uni-manager/patterns/list-page.md +29 -29
  70. package/src/teamix-evo-design-uni-manager/patterns/page-types.md +11 -11
  71. package/src/teamix-evo-design-uni-manager/philosophy.md +1 -1
  72. package/src/teamix-evo-design-uni-manager/rules/boundaries.rules.json +3 -3
  73. package/src/teamix-evo-manage/SKILL.md +288 -121
  74. package/src/teamix-evo-upgrade/SKILL.md +298 -0
  75. package/src/teamix-evo-design-opentrek/patterns/sidebar.md +0 -122
@@ -0,0 +1,1361 @@
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 Table List Reference)
9
+ ─────────────────────────────────────────────────────────────
10
+ 用途:AI 生成表格列表页时的基础参考,展示完整页面骨架与组件组合
11
+ ─────────────────────────────────────────────────────────────
12
+ 布局:TWO_COL(Sidebar + ContentCard 单例容器)
13
+ Sidebar:完整 L1 框架(折叠/展开 240px↔68px / flyout / sub-menu)
14
+ 内容区模式:D-TABLE(standard-list)
15
+ ─────────────────────────────────────────────────────────────
16
+ 组件清单:
17
+ • PageHeader
18
+ • ActionToolbar — 含 SearchCombo (来源 _shared/search-combo-spec.md)
19
+ • DataTable — 含 Checkbox列、可排序列、状态 Badge、操作列
20
+ • Pagination — 10 条/页,含前往跳转 + 每页选择器
21
+ ─────────────────────────────────────────────────────────────
22
+ 规范来源:pages/list-page/patterns/standard-list.md
23
+ Tokens 版本:v7.8.4(分层 padding 模型 v7.5 + v7.6 ContentCard 表面规则 + v7.8.4 字体族变量/Button token)
24
+ -->
25
+ <style>
26
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
27
+
28
+ :root {
29
+ --primary: 218.6 100% 46.7%;
30
+ --primary-foreground: 0 0% 100%;
31
+ --primary-hover: 218.6 100% 42%;
32
+ --primary-click: 218.6 100% 37.5%;
33
+ --gray-primary: 220 9% 7%;
34
+ --gray-primary-foreground: 210 33% 99%;
35
+ --gray-secondary-foreground: 216 4% 26%;
36
+ --muted-foreground: 216 2% 45%;
37
+ --gray-disabled: 210 3% 63%;
38
+ --gray-line: 210 9% 91%;
39
+ --gray-sidebar-accent: 210 12% 93%;
40
+ --gray-muted: 220 18% 97%;
41
+ --gray-white: 0 0% 100%;
42
+ /* Legacy 别名(v7.4 向后兼容) */
43
+ --gray-title: 220 9% 7%;
44
+ --gray-secondary: 216 4% 26%;
45
+ --gray-tertiary: 216 2% 45%;
46
+ --gray-border: 210 9% 91%;
47
+ --gray-fill: 220 18% 97%;
48
+ --gray-bg-dark: 220 18% 97%;
49
+ --gray-bg: 220 18% 97%;
50
+ --success: 142 76.6% 43.1%;
51
+ --success-bg: 142 84% 95%;
52
+ --success-border: 144 65% 76%;
53
+ --warning: 40 88% 48%;
54
+ --warning-bg: 43 100% 94%;
55
+ --warning-border: 40 92% 68%;
56
+ --destructive: 0 88.7% 53.1%;
57
+ --destructive-bg: 0 93.3% 97.5%;
58
+ --destructive-border: 0 88.7% 86.1%;
59
+ --info: 217 91% 53%;
60
+ --info-bg: 214 100% 97%;
61
+ --info-border: 213 94% 82.5%;
62
+ --background: 220 18% 97%;
63
+ --card: 0 0% 100%;
64
+ --card-foreground: 220 9% 7%;
65
+ --border: 210 9% 91%;
66
+ --ring: 218.6 100% 46.7%;
67
+ --sidebar: 220 18% 97%;
68
+ --sidebar-foreground: 220 9% 7%;
69
+ --sidebar-active: 210 12% 93%;
70
+ --sidebar-active-foreground: 220 9% 7%;
71
+ --sidebar-hover: 220 18% 97%;
72
+ --sidebar-group: 216 2% 45%;
73
+ --sidebar-item: 220 9% 7%;
74
+ --sidebar-item-muted: 216 4% 26%;
75
+ --sidebar-border: 210 9% 91%;
76
+ --radius: 1rem;
77
+ --radius-sm: 4px;
78
+ --radius-md: 8px;
79
+ --radius-lg: 12px;
80
+ --gap-xs: 4px;
81
+ --btn-padding-x: 16px;
82
+ --btn-padding-x-sm: 12px;
83
+ --button-gap: 8px;
84
+ --tabs-gap: 24px;
85
+ --card-gap: 16px;
86
+ --page-container-gap: 16px;
87
+ --page-container-padding: 16px;
88
+ --page-container-padding-left: 0px;
89
+ --card-padding-x: 20px;
90
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
91
+ --font-size-base: 12px;
92
+ --font-size-lg: 14px;
93
+ --font-size-xl: 16px;
94
+ --font-size-2xl: 18px;
95
+ --font-size-page-header: 18px;
96
+ --font-sans: 'PingFang SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
97
+ --font-mono: 'SF Mono', Monaco, 'Courier New', monospace;
98
+ --font-weight-normal: 400;
99
+ --font-weight-medium: 500;
100
+ --font-weight-black: 900;
101
+ --input-height: 32px;
102
+ --input-font-size: 12px;
103
+ --input-line-height: 18px;
104
+ --input-radius: 8px;
105
+ --layout-sidebar-width: 240px;
106
+ --duration-fast: 150ms;
107
+ --duration-normal: 200ms;
108
+ --easing-ease: ease;
109
+ }
110
+
111
+ body {
112
+ font-family: var(--font-sans);
113
+ font-size: var(--font-size-base);
114
+ color: hsl(var(--gray-primary));
115
+ background: hsl(var(--background));
116
+ min-height: 100vh;
117
+ }
118
+
119
+ /* ===== Sidebar ===== */
120
+ .sidebar {
121
+ position: fixed; top: 0; left: 0; bottom: 0;
122
+ width: var(--layout-sidebar-width);
123
+ background: hsl(var(--sidebar));
124
+ z-index: 100;
125
+ display: flex; flex-direction: column;
126
+ overflow: hidden;
127
+ transition: width 0.2s ease;
128
+ }
129
+ .sidebar-header {
130
+ height: 56px; padding: 0 16px;
131
+ display: flex; align-items: center; gap: var(--gap-xs);
132
+ flex-shrink: 0;
133
+ }
134
+ .sidebar-logo {
135
+ height: 28px; flex-shrink: 0;
136
+ display: flex; align-items: center;
137
+ overflow: hidden;
138
+ }
139
+ .sidebar-logo img {
140
+ height: 28px; width: auto; display: block;
141
+ }
142
+ .sidebar-logo .collapsed-logo {
143
+ display: none; width: 30px; height: 30px;
144
+ }
145
+ .sidebar-logo .expand-logo {
146
+ display: none; width: 18px; height: 18px;
147
+ color: hsl(var(--muted-foreground));
148
+ }
149
+ .sidebar-collapse-btn {
150
+ margin-left: auto; width: 18px; height: 18px;
151
+ background: none; border: none; padding: 0; cursor: pointer;
152
+ display: flex; align-items: center; justify-content: center;
153
+ color: hsl(var(--muted-foreground)); flex-shrink: 0;
154
+ }
155
+ .sidebar-collapse-btn svg { width: 18px; height: 18px; }
156
+
157
+ /* ===== Sidebar Collapsed State ===== */
158
+ .sidebar.collapsed { width: 68px; overflow: visible; }
159
+ .sidebar.collapsed .sidebar-header { padding: 0 12px; justify-content: center; }
160
+ .sidebar.collapsed .sidebar-logo { cursor: pointer; }
161
+ .sidebar.collapsed .sidebar-logo img { display: none; }
162
+ .sidebar.collapsed .sidebar-logo .collapsed-logo { display: block; }
163
+ .sidebar.collapsed .sidebar-logo:hover .collapsed-logo { display: none; }
164
+ .sidebar.collapsed .sidebar-logo:hover .expand-logo { display: block; }
165
+ .sidebar.collapsed .sidebar-collapse-btn { display: none; }
166
+ .sidebar.collapsed .sidebar-content { padding: 12px 0; overflow-y: auto; overflow-x: hidden; }
167
+ .sidebar.collapsed .sidebar-menu { padding: 0; }
168
+ .sidebar.collapsed .sidebar-menu > li { display: flex; justify-content: center; }
169
+ .sidebar.collapsed .sidebar-menu > li > a.sidebar-menu-item {
170
+ justify-content: center; padding: 0; width: 40px; height: 40px;
171
+ border-radius: var(--radius-md); position: relative; flex-shrink: 0;
172
+ gap: 0;
173
+ }
174
+ .sidebar.collapsed .sidebar-menu > li > a.sidebar-menu-item .item-icon { margin: 0; }
175
+ .sidebar.collapsed .sidebar-menu > li > a > span:not(.item-icon),
176
+ .sidebar.collapsed .sidebar-menu > li > a > .expand-icon,
177
+ .sidebar.collapsed .sidebar-menu-item:not(.item-icon):after { display: none; }
178
+ /* 隐藏文字节点(直接文本) */
179
+ .sidebar.collapsed .sidebar-menu > li > a { font-size: 0; }
180
+ .sidebar.collapsed .sidebar-menu > li > a > .item-icon { font-size: var(--font-size-lg); }
181
+ .sidebar.collapsed .sidebar-sub-menu { display: none !important; }
182
+ .sidebar.collapsed .sidebar-divider { margin: 0 14px; }
183
+ .sidebar.collapsed .sidebar-footer { padding: 12px 10px 16px; justify-content: center; }
184
+ .sidebar.collapsed .sidebar-username,
185
+ .sidebar.collapsed .sidebar-more { display: none; }
186
+
187
+ /* ===== Sidebar Collapsed Flyout (hover 浮层展示二级菜单) ===== */
188
+ .sidebar.collapsed .sidebar-menu > li { position: relative; }
189
+ .sidebar.collapsed .sidebar-menu > li:hover > .sidebar-flyout {
190
+ display: block;
191
+ }
192
+ .sidebar-flyout {
193
+ display: none;
194
+ position: absolute; left: 100%; top: 0;
195
+ min-width: 160px; padding: 4px;
196
+ background: hsl(var(--card));
197
+ border: 1px solid hsl(var(--gray-line));
198
+ border-radius: var(--radius-md);
199
+ box-shadow: 0 4px 12px rgba(0,0,0,0.08);
200
+ z-index: 200;
201
+ }
202
+ .sidebar-flyout .sidebar-menu-item {
203
+ height: 36px; padding: 0 12px;
204
+ font-size: var(--font-size-base) !important;
205
+ font-weight: var(--font-weight-normal);
206
+ color: hsl(var(--gray-primary));
207
+ border-radius: var(--radius-sm);
208
+ white-space: nowrap;
209
+ }
210
+ .sidebar-flyout .sidebar-menu-item:hover {
211
+ background: hsl(var(--gray-muted));
212
+ }
213
+
214
+ /* Main content 响应 sidebar 收起 */
215
+ .main-content {
216
+ margin-left: var(--layout-sidebar-width);
217
+ min-height: 100vh;
218
+ transition: margin-left 0.2s ease;
219
+ }
220
+ .sidebar.collapsed ~ .main-content { margin-left: 68px; }
221
+ .sidebar-product {
222
+ font-size: var(--font-size-xl); font-weight: var(--font-weight-black);
223
+ color: hsl(var(--sidebar-foreground)); letter-spacing: -0.025em;
224
+ white-space: nowrap; line-height: 1;
225
+ }
226
+ .sidebar-content {
227
+ flex: 1; overflow-y: auto; padding: 12px 0;
228
+ }
229
+ /* twoLevel 变体:去掉 sidebar-group-label,主菜单之间间距由 li padding 提供;保留 sidebar-group-label 样式以兼容其他页面。 */
230
+ .sidebar-group-label {
231
+ padding: 0 16px; height: 26px; line-height: 26px;
232
+ font-size: var(--font-size-base); font-weight: var(--font-weight-normal);
233
+ color: hsl(var(--sidebar-group));
234
+ }
235
+ .sidebar-group-label:not(:first-child) { margin-top: 16px; }
236
+ .sidebar-menu { list-style: none; padding: 0 8px; display: flex; flex-direction: column; gap: 2px; }
237
+ .sidebar-menu-item {
238
+ display: flex; align-items: center; gap: var(--button-gap);
239
+ padding: 0 12px; height: 40px;
240
+ border-radius: var(--radius-md);
241
+ font-size: var(--font-size-lg); font-weight: var(--font-weight-medium);
242
+ color: hsl(var(--sidebar-item-muted));
243
+ text-decoration: none; cursor: pointer;
244
+ transition: background var(--duration-fast), color var(--duration-fast);
245
+ white-space: nowrap;
246
+ }
247
+ .sidebar-menu-item:hover {
248
+ background: hsl(var(--sidebar-hover));
249
+ color: hsl(var(--sidebar-item));
250
+ }
251
+ .sidebar-menu-item.active {
252
+ background: hsl(var(--sidebar-active));
253
+ color: hsl(var(--sidebar-active-foreground));
254
+ }
255
+ .sidebar-menu-item .item-icon {
256
+ width: 16px; height: 16px; flex-shrink: 0;
257
+ display: flex; align-items: center; justify-content: center;
258
+ }
259
+ .sidebar-menu-item .expand-icon {
260
+ margin-left: auto; width: 16px; height: 16px;
261
+ transition: transform var(--duration-fast);
262
+ }
263
+ .sidebar-menu-item.expanded .expand-icon { transform: rotate(90deg); }
264
+ .sidebar-sub-menu {
265
+ list-style: none; padding: 0 0 0 24px;
266
+ display: none;
267
+ }
268
+ /* twoLevel 变体:子菜单与主菜单在同一列上下对齐。sub-menu 不再加左 padding,由子菜单项自身 padding-left = 12+16+8 = 36px 代替主菜单的 "padding+icon+gap",使文字起点 x 坐标与主菜单一致。 */
269
+ .sidebar-menu > li > .sidebar-sub-menu { padding-left: 0; }
270
+ .sidebar-sub-menu.show { display: block; }
271
+ .sidebar-sub-menu .sidebar-menu-item {
272
+ height: 36px; font-size: var(--font-size-base); font-weight: var(--font-weight-normal);
273
+ /* twoLevel 变体对齐规则:36px = 主菜单 padding-left(12) + item-icon(16) + gap(8),使文字与主菜单文字上下对齐。 */
274
+ padding-left: 36px;
275
+ }
276
+ .sidebar-divider {
277
+ height: 1px; background: hsl(var(--sidebar-border));
278
+ margin: 0 12px; flex-shrink: 0;
279
+ }
280
+ .sidebar-footer {
281
+ flex-shrink: 0; display: flex; align-items: center;
282
+ gap: var(--button-gap); padding: 12px 12px 16px 24px;
283
+ }
284
+ .sidebar-avatar {
285
+ width: 28px; height: 28px; border-radius: 50%;
286
+ background: hsl(var(--sidebar-active));
287
+ display: flex; align-items: center; justify-content: center;
288
+ font-size: var(--font-size-base); font-weight: 600;
289
+ color: hsl(var(--gray-primary)); flex-shrink: 0;
290
+ }
291
+ .sidebar-username {
292
+ flex: 1; font-size: var(--font-size-lg); font-weight: var(--font-weight-medium);
293
+ color: hsl(var(--sidebar-item)); overflow: hidden;
294
+ text-overflow: ellipsis; white-space: nowrap;
295
+ }
296
+ .sidebar-more {
297
+ width: 28px; height: 28px; border-radius: var(--radius-sm);
298
+ border: none; background: transparent; cursor: pointer;
299
+ display: flex; align-items: center; justify-content: center;
300
+ color: hsl(var(--muted-foreground));
301
+ }
302
+ .sidebar-more:hover { background: hsl(var(--sidebar-hover)); color: hsl(var(--gray-primary)); }
303
+
304
+ /* ===== Main Content ===== */
305
+
306
+ .page-container {
307
+ padding: var(--page-container-padding) var(--page-container-padding) var(--page-container-padding) var(--page-container-padding-left);
308
+ }
309
+
310
+ .content-wrapper {
311
+ /* wraps the card */
312
+ }
313
+
314
+ .card {
315
+ background: hsl(var(--card));
316
+ border-radius: var(--radius-lg);
317
+ /* v7.6:去掉 1px border,使用最弱阴影 var(--shadow-sm) */
318
+ box-shadow: var(--shadow-sm, 0 1px 2px rgba(0,0,0,0.05));
319
+ overflow: hidden;
320
+ display: flex; flex-direction: column;
321
+ max-height: calc(100vh - 40px);
322
+ padding: 0 var(--card-padding-x);
323
+ }
324
+ .card-scrollable {
325
+ overflow-y: auto; flex: 1;
326
+ --wc-pad-x: var(--card-padding-x);
327
+ }
328
+
329
+ /* ===== PageHeader ===== */
330
+ .sticky-wrapper {
331
+ position: sticky; top: 0; z-index: 10;
332
+ flex-shrink: 0;
333
+ }
334
+ .page-header {
335
+ height: 64px; padding: 0;
336
+ display: flex; align-items: center; justify-content: space-between;
337
+ background: transparent; border: none;
338
+ }
339
+ .page-header-title {
340
+ font-size: var(--font-size-page-header); font-weight: var(--font-weight-medium);
341
+ line-height: 36px; color: hsl(var(--gray-primary));
342
+ }
343
+
344
+ /* ===== ActionToolbar ===== */
345
+ .action-toolbar {
346
+ display: flex; align-items: center; gap: var(--button-gap);
347
+ padding: 0;
348
+ padding-bottom: var(--card-gap);
349
+ flex-wrap: nowrap;
350
+ }
351
+ .action-toolbar .left-actions { display: flex; align-items: center; gap: var(--button-gap); }
352
+ /* SearchCombo 固定跟随在 left-actions 后方,间距 20px(独立于 toolbar gap) */
353
+ .action-toolbar > .search-combo { margin-left: calc(20px - var(--button-gap)); }
354
+ .action-toolbar .right-tools {
355
+ margin-left: auto; display: flex; align-items: center; gap: var(--button-gap);
356
+ }
357
+
358
+ /* ===== Buttons ===== */
359
+ .btn {
360
+ display: inline-flex; align-items: center; justify-content: center; gap: var(--gap-xs);
361
+ height: var(--input-height); padding: 0 var(--btn-padding-x);
362
+ border-radius: var(--radius-md); font-size: var(--font-size-base);
363
+ font-weight: var(--font-weight-medium); border: none; cursor: pointer;
364
+ transition: background var(--duration-fast), opacity var(--duration-fast);
365
+ white-space: nowrap;
366
+ }
367
+ .btn-primary {
368
+ background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
369
+ }
370
+ .btn-primary:hover { background: hsl(var(--primary-hover)); }
371
+ .btn-outline {
372
+ background: transparent; color: hsl(var(--gray-primary));
373
+ border: 1px solid hsl(var(--gray-line));
374
+ }
375
+ .btn-outline:hover { background: hsl(var(--gray-muted)); }
376
+ .btn-ghost {
377
+ background: transparent; color: hsl(var(--gray-secondary-foreground));
378
+ }
379
+ .btn-ghost:hover { background: hsl(var(--gray-muted)); color: hsl(var(--gray-primary)); }
380
+ .btn-icon {
381
+ width: var(--input-height); height: var(--input-height); padding: 0;
382
+ background: transparent; border: 1px solid hsl(var(--gray-line));
383
+ border-radius: var(--radius-md); cursor: pointer;
384
+ display: flex; align-items: center; justify-content: center;
385
+ color: hsl(var(--gray-secondary-foreground));
386
+ }
387
+ .btn-icon:hover { background: hsl(var(--gray-muted)); color: hsl(var(--gray-primary)); }
388
+
389
+ /* ===== SearchCombo ===== */
390
+ .search-combo {
391
+ display: flex; align-items: center;
392
+ border: 1px solid hsl(var(--border)); border-radius: 6px;
393
+ height: var(--input-height); min-width: 200px;
394
+ transition: border-color var(--duration-fast);
395
+ }
396
+ .search-combo:focus-within {
397
+ border-color: hsl(var(--primary));
398
+ box-shadow: 0 0 0 2px hsl(var(--primary) / 0.1);
399
+ }
400
+ .search-combo:hover { border-color: hsl(var(--muted-foreground)); }
401
+ .search-dimension {
402
+ border: none; padding-left: 8px; font-size: var(--font-size-base);
403
+ color: hsl(var(--gray-primary)); outline: none; appearance: none;
404
+ background: transparent; cursor: pointer; min-width: 60px;
405
+ }
406
+ .search-divider {
407
+ width: 1px; height: 20px; background: hsl(var(--border)); flex-shrink: 0;
408
+ }
409
+ .search-input {
410
+ border: none; padding: 0 8px; font-size: var(--font-size-base);
411
+ color: hsl(var(--gray-primary)); outline: none; flex: 1;
412
+ min-width: 80px; background: transparent;
413
+ }
414
+ .search-input::placeholder { color: hsl(var(--muted-foreground)); }
415
+ .search-btn {
416
+ border: none; background: transparent; padding: 0 8px;
417
+ cursor: pointer; display: flex; align-items: center; justify-content: center;
418
+ color: hsl(var(--gray-secondary-foreground));
419
+ }
420
+ .search-btn:hover { color: hsl(var(--primary)); }
421
+
422
+ /* ===== DataTable ===== */
423
+ .data-table-wrapper {
424
+ width: 100%; overflow-x: auto;
425
+ }
426
+ .data-table {
427
+ width: 100%; border-collapse: collapse;
428
+ }
429
+ .data-table thead th {
430
+ padding: 12px 12px; text-align: left;
431
+ font-size: var(--font-size-base); font-weight: var(--font-weight-medium);
432
+ color: hsl(var(--gray-secondary-foreground));
433
+ background: hsl(var(--gray-muted));
434
+ border-bottom: 1px solid hsl(var(--gray-line));
435
+ white-space: nowrap; position: sticky; top: 0; z-index: 5;
436
+ user-select: none;
437
+ }
438
+ .data-table thead th.sortable { cursor: pointer; }
439
+ .data-table thead th.sortable:hover { color: hsl(var(--gray-primary)); }
440
+ .data-table thead th .sort-icon {
441
+ display: inline-block; width: 12px; height: 12px; margin-left: 4px;
442
+ vertical-align: middle; opacity: 0.4;
443
+ }
444
+ .data-table thead th.sort-active .sort-icon { opacity: 1; color: hsl(var(--primary)); }
445
+ .data-table tbody tr {
446
+ border-bottom: 1px solid hsl(var(--gray-line));
447
+ transition: background var(--duration-fast);
448
+ }
449
+ /* dropdown 展开时提升当前行层级,防止浮层被下方行遮挡 */
450
+ .data-table tbody tr.has-dropdown-open { position: relative; z-index: 10; }
451
+ .data-table tbody tr:hover { background: hsl(var(--gray-muted)); }
452
+ .data-table tbody tr:hover td.col-action { background: hsl(var(--gray-muted)); }
453
+ .data-table tbody tr:last-child { border-bottom: none; }
454
+ .data-table tbody td {
455
+ padding: 12px 12px; font-size: var(--font-size-base);
456
+ color: hsl(var(--gray-primary)); vertical-align: middle;
457
+ }
458
+ /* 首末列额外缩进 20px:表格作为密集数据区,享有除 Card.paddingX 之外的 20px 视觉呈息 */
459
+ .data-table thead th:first-child,
460
+ .data-table tbody td:first-child { padding-left: 20px; }
461
+ .data-table thead th:last-child,
462
+ .data-table tbody td:last-child { padding-right: 20px; }
463
+ .data-table .col-checkbox { width: 48px; }
464
+ .data-table .col-id { min-width: 200px; }
465
+ .data-table .col-status { min-width: 100px; }
466
+ .data-table .col-access { min-width: 220px; }
467
+ .data-table .col-version { min-width: 100px; }
468
+ .data-table .col-cluster { min-width: 120px; }
469
+ .data-table .col-namespace { min-width: 140px; }
470
+ .data-table .col-time { min-width: 160px; }
471
+ .data-table .col-action { width: 140px; text-align: left; position: sticky; right: 0; background: hsl(var(--card)); }
472
+ .data-table thead th.col-action { background: hsl(var(--gray-muted)); text-align: left; }
473
+ .data-table tbody td.col-action { text-align: left; }
474
+
475
+ /* ===== Checkbox ===== */
476
+ .checkbox {
477
+ width: 16px; height: 16px; border-radius: var(--radius-sm);
478
+ border: 1px solid hsl(var(--gray-line)); cursor: pointer;
479
+ appearance: none; -webkit-appearance: none;
480
+ display: inline-flex; align-items: center; justify-content: center;
481
+ transition: all var(--duration-fast);
482
+ }
483
+ .checkbox:checked {
484
+ background: hsl(var(--primary)); border-color: hsl(var(--primary));
485
+ }
486
+ .checkbox:checked::after {
487
+ content: ''; width: 10px; height: 7px;
488
+ border-left: 2px solid white; border-bottom: 2px solid white;
489
+ transform: rotate(-45deg) translateY(-1px);
490
+ }
491
+
492
+ /* ===== Status Badge ===== */
493
+ .status-badge {
494
+ display: inline-flex; align-items: center; gap: 6px;
495
+ font-size: var(--font-size-base);
496
+ }
497
+ .status-dot {
498
+ width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
499
+ }
500
+ .status-running .status-dot { background: hsl(var(--success)); }
501
+ .status-creating .status-dot { background: hsl(var(--warning)); animation: pulse 1.5s infinite; }
502
+ .status-stopped .status-dot { background: hsl(var(--gray-disabled)); }
503
+ .status-error .status-dot { background: hsl(var(--destructive)); }
504
+ @keyframes pulse {
505
+ 0%, 100% { opacity: 1; }
506
+ 50% { opacity: 0.5; }
507
+ }
508
+
509
+ /* ===== Access Display ===== */
510
+ .access-item {
511
+ display: inline-flex; align-items: center; gap: 4px;
512
+ }
513
+ .access-tag {
514
+ display: inline-flex; align-items: center; justify-content: center;
515
+ padding: 1px 4px; border-radius: var(--radius-sm);
516
+ font-size: 10px; font-weight: var(--font-weight-medium);
517
+ line-height: 16px;
518
+ }
519
+ .access-tag.public {
520
+ background: hsl(var(--warning-bg)); color: hsl(var(--warning));
521
+ border: 1px solid hsl(var(--warning-border));
522
+ }
523
+ .access-tag.private {
524
+ background: hsl(var(--info-bg)); color: hsl(var(--info));
525
+ border: 1px solid hsl(var(--info-border));
526
+ }
527
+ .access-ip {
528
+ font-family: var(--font-mono);
529
+ font-size: var(--font-size-base); color: hsl(var(--gray-secondary-foreground));
530
+ }
531
+ .access-type {
532
+ font-size: var(--font-size-base); color: hsl(var(--muted-foreground));
533
+ }
534
+ .access-list { display: flex; flex-direction: column; gap: 2px; }
535
+
536
+ /* ===== ID/Name ===== */
537
+ .id-name { display: flex; flex-direction: column; gap: 2px; }
538
+ .id-name-name {
539
+ font-size: var(--font-size-base); font-weight: var(--font-weight-medium);
540
+ color: hsl(var(--gray-primary));
541
+ }
542
+ .id-name-id {
543
+ font-family: var(--font-mono);
544
+ font-size: var(--font-size-base); color: hsl(var(--gray-secondary-foreground));
545
+ }
546
+
547
+ /* ===== Action Column ===== */
548
+ .action-link {
549
+ color: hsl(var(--primary)); text-decoration: none; cursor: pointer;
550
+ font-size: var(--font-size-base); vertical-align: middle;
551
+ }
552
+ .action-link:hover { text-decoration: underline; }
553
+ .action-divider {
554
+ display: inline-block; width: 1px; height: 12px;
555
+ background: hsl(var(--gray-line)); margin: 0 6px; vertical-align: middle;
556
+ }
557
+ .action-more {
558
+ position: relative; display: inline-block; vertical-align: middle;
559
+ }
560
+ .action-more-btn {
561
+ border: none; background: transparent; cursor: pointer;
562
+ color: hsl(var(--gray-secondary-foreground)); padding: 2px 4px;
563
+ border-radius: var(--radius-sm); font-size: var(--font-size-base);
564
+ }
565
+ .action-more-btn:hover { background: hsl(var(--gray-muted)); }
566
+ .action-dropdown {
567
+ position: absolute; right: 0; top: 100%; z-index: 50;
568
+ background: hsl(var(--card)); border: 1px solid hsl(var(--gray-line));
569
+ border-radius: var(--radius-md); box-shadow: 0 6px 24px rgba(0,0,0,0.1);
570
+ display: none; padding: 4px; min-width: 128px; white-space: nowrap;
571
+ }
572
+ .action-dropdown.show { display: block; }
573
+ .action-dropdown-item {
574
+ display: flex; align-items: center; width: 100%; height: 32px; padding: 0 8px;
575
+ font-size: var(--font-size-base); text-align: left;
576
+ border: none; background: transparent; cursor: pointer;
577
+ color: hsl(var(--gray-primary)); border-radius: var(--radius-sm);
578
+ white-space: nowrap;
579
+ }
580
+ .action-dropdown-item:hover { background: hsl(var(--gray-muted)); }
581
+ .action-dropdown-item.danger { color: hsl(var(--destructive)); }
582
+ .action-dropdown-item.danger:hover { background: hsl(var(--destructive) / 0.1); }
583
+ .action-dropdown-item:disabled {
584
+ opacity: 0.45; pointer-events: none; cursor: not-allowed;
585
+ }
586
+
587
+ /* ===== BulkActionBar ===== */
588
+ .bulk-action-bar {
589
+ display: none; align-items: center; gap: var(--button-gap);
590
+ padding: 10px 0; background: #ffffff;
591
+ border-top: 1px solid hsl(var(--gray-line));
592
+ }
593
+ .bulk-action-bar.show { display: flex; }
594
+ .bulk-action-bar .bulk-actions { display: flex; gap: var(--button-gap); }
595
+ .bulk-action-bar .bulk-stats {
596
+ display: flex; align-items: center; gap: 16px;
597
+ margin-left: auto;
598
+ }
599
+ .bulk-action-bar .bulk-count {
600
+ font-size: var(--font-size-base); color: hsl(var(--gray-secondary-foreground));
601
+ }
602
+
603
+ /* ===== Pagination ===== */
604
+ .pagination-wrapper {
605
+ display: flex; align-items: center; justify-content: space-between;
606
+ padding: 12px 0; border-top: 1px solid hsl(var(--gray-line));
607
+ }
608
+ .pagination-info {
609
+ font-size: var(--font-size-base); color: hsl(var(--gray-secondary-foreground));
610
+ }
611
+ .pagination-right {
612
+ display: flex; align-items: center; gap: var(--button-gap);
613
+ }
614
+ .pagination {
615
+ display: flex; align-items: center; gap: 4px;
616
+ }
617
+ .pagination-btn {
618
+ min-width: 32px; height: 32px; padding: 0 6px;
619
+ border: 1px solid hsl(var(--gray-line)); border-radius: var(--radius-md);
620
+ background: hsl(var(--card)); color: hsl(var(--gray-primary));
621
+ font-size: var(--font-size-base); cursor: pointer;
622
+ display: flex; align-items: center; justify-content: center;
623
+ transition: all var(--duration-fast);
624
+ }
625
+ .pagination-btn:hover:not(:disabled):not(.active) {
626
+ background: hsl(var(--gray-muted)); border-color: hsl(var(--gray-line));
627
+ }
628
+ .pagination-btn.active {
629
+ background: hsl(var(--primary)); color: hsl(var(--primary-foreground));
630
+ border-color: hsl(var(--primary)); font-weight: var(--font-weight-medium);
631
+ }
632
+ .pagination-btn:disabled {
633
+ opacity: 0.45; cursor: not-allowed;
634
+ }
635
+ .pagination-size select {
636
+ height: var(--input-height); padding: 0 8px;
637
+ border: 1px solid hsl(var(--gray-line)); border-radius: var(--radius-md);
638
+ font-size: var(--font-size-base); color: hsl(var(--gray-primary));
639
+ background: hsl(var(--card)); appearance: none; cursor: pointer;
640
+ }
641
+ .pagination-goto {
642
+ display: flex; align-items: center; gap: 4px;
643
+ margin-left: 4px;
644
+ font-size: var(--font-size-base); color: hsl(var(--gray-secondary-foreground));
645
+ }
646
+ .pagination-goto-input {
647
+ width: 40px; height: 32px; padding: 0 4px;
648
+ border: 1px solid hsl(var(--gray-line)); border-radius: var(--radius-md);
649
+ font-size: var(--font-size-base); text-align: center;
650
+ color: hsl(var(--gray-primary)); background: hsl(var(--card));
651
+ outline: none; transition: border-color 200ms;
652
+ }
653
+ .pagination-goto-input:focus {
654
+ border-color: hsl(var(--primary));
655
+ }
656
+ .pagination-goto-btn {
657
+ display: inline-flex; align-items: center; justify-content: center;
658
+ height: 32px; padding: 0 8px;
659
+ border-radius: var(--radius-md); border: none;
660
+ background: transparent; color: hsl(var(--gray-secondary-foreground));
661
+ cursor: pointer; font-size: var(--font-size-base); font-family: inherit;
662
+ transition: background var(--duration-fast), color var(--duration-fast);
663
+ }
664
+ .pagination-goto-btn:hover {
665
+ background: hsl(var(--gray-muted)); color: hsl(var(--gray-primary));
666
+ }
667
+ </style>
668
+ </head>
669
+ <body>
670
+
671
+ <!-- ===== Sidebar ===== -->
672
+ <aside class="sidebar">
673
+ <div class="sidebar-header">
674
+ <div class="sidebar-logo" id="sidebarLogoArea">
675
+ <img src="./_assets/op-ai-gateway-logo.svg" alt="OP AI Gateway" />
676
+ <img class="collapsed-logo" src="./_assets/op-logo-collapsed.png" alt="OP" />
677
+ <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>
678
+ </div>
679
+ <button class="sidebar-collapse-btn" title="收起侧边栏">
680
+ <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>
681
+ </button>
682
+ </div>
683
+
684
+ <!-- sidebar:按 PRD 导航结构,L1 为一级菜单,L2 为二级子菜单。 -->
685
+ <div class="sidebar-content">
686
+ <ul class="sidebar-menu">
687
+ <!-- L1: AI 网关(叶子菜单,当前活跃) -->
688
+ <li><a class="sidebar-menu-item active" href="#">
689
+ <span class="item-icon">
690
+ <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>
691
+ </span>
692
+ AI 网关
693
+ </a></li>
694
+
695
+ <!-- L1: API 网关(父菜单) -->
696
+ <li>
697
+ <a class="sidebar-menu-item expanded" href="#" data-toggle="api-gateway">
698
+ <span class="item-icon">
699
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
700
+ </span>
701
+ API 网关
702
+ <span class="expand-icon">
703
+ <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>
704
+ </span>
705
+ </a>
706
+ <ul class="sidebar-sub-menu show" id="api-gateway">
707
+ <li><a class="sidebar-menu-item" href="#">实例管理</a></li>
708
+ <li><a class="sidebar-menu-item" href="#">共享实例管理</a></li>
709
+ </ul>
710
+ <div class="sidebar-flyout">
711
+ <a class="sidebar-menu-item" href="#">实例管理</a>
712
+ <a class="sidebar-menu-item" href="#">共享实例管理</a>
713
+ </div>
714
+ </li>
715
+
716
+ <!-- L1: 云原生网关(父菜单) -->
717
+ <li>
718
+ <a class="sidebar-menu-item" href="#" data-toggle="cloud-native-gw">
719
+ <span class="item-icon">
720
+ <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>
721
+ </span>
722
+ 云原生网关
723
+ <span class="expand-icon">
724
+ <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>
725
+ </span>
726
+ </a>
727
+ <ul class="sidebar-sub-menu" id="cloud-native-gw">
728
+ <li><a class="sidebar-menu-item" href="#">实例列表</a></li>
729
+ <li><a class="sidebar-menu-item" href="#">Nginx Ingress 迁移</a></li>
730
+ </ul>
731
+ <div class="sidebar-flyout">
732
+ <a class="sidebar-menu-item" href="#">实例列表</a>
733
+ <a class="sidebar-menu-item" href="#">Nginx Ingress 迁移</a>
734
+ </div>
735
+ </li>
736
+
737
+ <!-- L1: 集群管理(叶子菜单) -->
738
+ <li><a class="sidebar-menu-item" href="#">
739
+ <span class="item-icon">
740
+ <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>
741
+ </span>
742
+ 集群管理
743
+ </a></li>
744
+
745
+ <!-- L1: 级联管理(父菜单) -->
746
+ <li>
747
+ <a class="sidebar-menu-item" href="#" data-toggle="cascade-mgmt">
748
+ <span class="item-icon">
749
+ <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>
750
+ </span>
751
+ 级联管理
752
+ <span class="expand-icon">
753
+ <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>
754
+ </span>
755
+ </a>
756
+ <ul class="sidebar-sub-menu" id="cascade-mgmt">
757
+ <li><a class="sidebar-menu-item" href="#">级联网关</a></li>
758
+ <li><a class="sidebar-menu-item" href="#">级联链路</a></li>
759
+ </ul>
760
+ <div class="sidebar-flyout">
761
+ <a class="sidebar-menu-item" href="#">级联网关</a>
762
+ <a class="sidebar-menu-item" href="#">级联链路</a>
763
+ </div>
764
+ </li>
765
+
766
+ <!-- L1: 容灾管理(父菜单) -->
767
+ <li>
768
+ <a class="sidebar-menu-item" href="#" data-toggle="disaster-mgmt">
769
+ <span class="item-icon">
770
+ <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>
771
+ </span>
772
+ 容灾管理
773
+ <span class="expand-icon">
774
+ <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>
775
+ </span>
776
+ </a>
777
+ <ul class="sidebar-sub-menu" id="disaster-mgmt">
778
+ <li><a class="sidebar-menu-item" href="#">容灾网关实例</a></li>
779
+ </ul>
780
+ <div class="sidebar-flyout">
781
+ <a class="sidebar-menu-item" href="#">容灾网关实例</a>
782
+ </div>
783
+ </li>
784
+
785
+ <!-- L1: 认证授权(父菜单) -->
786
+ <li>
787
+ <a class="sidebar-menu-item" href="#" data-toggle="auth-mgmt">
788
+ <span class="item-icon">
789
+ <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>
790
+ </span>
791
+ 认证授权
792
+ <span class="expand-icon">
793
+ <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>
794
+ </span>
795
+ </a>
796
+ <ul class="sidebar-sub-menu" id="auth-mgmt">
797
+ <li><a class="sidebar-menu-item" href="#">用户管理</a></li>
798
+ <li><a class="sidebar-menu-item" href="#">角色管理</a></li>
799
+ <li><a class="sidebar-menu-item" href="#">权限插件</a></li>
800
+ <li><a class="sidebar-menu-item" href="#">访问凭证</a></li>
801
+ </ul>
802
+ <div class="sidebar-flyout">
803
+ <a class="sidebar-menu-item" href="#">用户管理</a>
804
+ <a class="sidebar-menu-item" href="#">角色管理</a>
805
+ <a class="sidebar-menu-item" href="#">权限插件</a>
806
+ <a class="sidebar-menu-item" href="#">访问凭证</a>
807
+ </div>
808
+ </li>
809
+ </ul>
810
+ </div>
811
+
812
+ <div class="sidebar-divider"></div>
813
+ <div class="sidebar-footer">
814
+ <div class="sidebar-avatar">A</div>
815
+ <span class="sidebar-username">admin</span>
816
+ <button class="sidebar-more">
817
+ <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>
818
+ </button>
819
+ </div>
820
+ </aside>
821
+
822
+ <!-- ===== Main Content ===== -->
823
+ <div class="main-content">
824
+ <div class="page-container">
825
+ <div class="content-wrapper">
826
+ <div class="card">
827
+ <div class="card-scrollable">
828
+
829
+ <!-- PageHeader -->
830
+ <div class="sticky-wrapper">
831
+ <header class="page-header">
832
+ <h1 class="page-header-title">网关实例</h1>
833
+ </header>
834
+ </div>
835
+
836
+ <!-- ActionToolbar -->
837
+ <div class="action-toolbar">
838
+ <div class="left-actions">
839
+ <button class="btn btn-primary">
840
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>
841
+ 创建实例
842
+ </button>
843
+ </div>
844
+ <!-- SearchCombo 紧跟 left-actions 后方,间距 20px -->
845
+ <div class="search-combo">
846
+ <select class="search-dimension" id="searchDimension">
847
+ <option value="all">全部</option>
848
+ <option value="id">实例 ID/名称</option>
849
+ <option value="name">实例名称</option>
850
+ </select>
851
+ <span class="search-divider"></span>
852
+ <input class="search-input" id="searchInput" placeholder="搜索实例 ID / 名称" />
853
+ <button class="search-btn" id="searchBtn">
854
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
855
+ </button>
856
+ </div>
857
+ <div class="right-tools">
858
+ <button class="btn-icon" title="刷新">
859
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
860
+ </button>
861
+ </div>
862
+ </div>
863
+
864
+ <!-- DataTable -->
865
+ <div class="data-table-wrapper">
866
+ <table class="data-table">
867
+ <thead>
868
+ <tr>
869
+ <th class="col-checkbox"><input type="checkbox" class="checkbox" id="selectAll" /></th>
870
+ <th class="col-id sortable sort-active" data-sort="id">
871
+ 实例 ID/名称
872
+ <span class="sort-icon">
873
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7-7 7 7"/></svg>
874
+ </span>
875
+ </th>
876
+ <th class="col-status sortable" data-sort="status">
877
+ 实例状态
878
+ <span class="sort-icon">
879
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7-7 7 7"/></svg>
880
+ </span>
881
+ </th>
882
+ <th class="col-access">对外访问</th>
883
+ <th class="col-version">引擎版本</th>
884
+ <th class="col-cluster">集群</th>
885
+ <th class="col-namespace">命名空间</th>
886
+ <th class="col-time sortable" data-sort="time">
887
+ 创建时间
888
+ <span class="sort-icon">
889
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7-7 7 7"/></svg>
890
+ </span>
891
+ </th>
892
+ <th class="col-action">操作</th>
893
+ </tr>
894
+ </thead>
895
+ <tbody id="tableBody">
896
+ <!-- Row 1: 运行中 -->
897
+ <tr>
898
+ <td><input type="checkbox" class="checkbox" /></td>
899
+ <td>
900
+ <div class="id-name">
901
+ <span class="id-name-name">ai-gateway-prod</span>
902
+ <span class="id-name-id">agw-uf6a8b3c2d1e0f4a</span>
903
+ </div>
904
+ </td>
905
+ <td>
906
+ <span class="status-badge status-running">
907
+ <span class="status-dot"></span>
908
+ 运行中
909
+ </span>
910
+ </td>
911
+ <td>
912
+ <div class="access-list">
913
+ <div class="access-item">
914
+ <span class="access-tag public">公</span>
915
+ <span class="access-ip">47.98.123.45</span>
916
+ <span class="access-type">公网</span>
917
+ </div>
918
+ <div class="access-item">
919
+ <span class="access-tag private">内</span>
920
+ <span class="access-ip">172.16.0.12</span>
921
+ <span class="access-type">内网</span>
922
+ </div>
923
+ </div>
924
+ </td>
925
+ <td><span style="font-family: var(--font-mono);">2.1.11</span></td>
926
+ <td>prod-cluster-01</td>
927
+ <td>ai-gateway</td>
928
+ <td>2026-05-10 14:30:00</td>
929
+ <td class="col-action">
930
+ <a class="action-link" href="#">详情</a>
931
+ <span class="action-divider"></span>
932
+ <span class="action-more">
933
+ <button class="action-more-btn" onclick="toggleDropdown(this)">
934
+ <svg width="16" height="16" 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>
935
+ </button>
936
+ <div class="action-dropdown">
937
+ <button class="action-dropdown-item">重启</button>
938
+ <button class="action-dropdown-item">停止</button>
939
+ <button class="action-dropdown-item danger">删除</button>
940
+ </div>
941
+ </span>
942
+ </td>
943
+ </tr>
944
+
945
+ <!-- Row 2: 运行中 -->
946
+ <tr>
947
+ <td><input type="checkbox" class="checkbox" /></td>
948
+ <td>
949
+ <div class="id-name">
950
+ <span class="id-name-name">ai-gateway-staging</span>
951
+ <span class="id-name-id">agw-7c9d2e4f5a6b1c3d</span>
952
+ </div>
953
+ </td>
954
+ <td>
955
+ <span class="status-badge status-running">
956
+ <span class="status-dot"></span>
957
+ 运行中
958
+ </span>
959
+ </td>
960
+ <td>
961
+ <div class="access-list">
962
+ <div class="access-item">
963
+ <span class="access-tag public">公</span>
964
+ <span class="access-ip">47.98.200.88</span>
965
+ <span class="access-type">公网</span>
966
+ </div>
967
+ <div class="access-item">
968
+ <span class="access-tag private">内</span>
969
+ <span class="access-ip">172.16.1.25</span>
970
+ <span class="access-type">内网</span>
971
+ </div>
972
+ </div>
973
+ </td>
974
+ <td><span style="font-family: var(--font-mono);">2.1.11</span></td>
975
+ <td>staging-cluster-01</td>
976
+ <td>ai-gateway-test</td>
977
+ <td>2026-05-12 09:15:00</td>
978
+ <td class="col-action">
979
+ <a class="action-link" href="#">详情</a>
980
+ <span class="action-divider"></span>
981
+ <span class="action-more">
982
+ <button class="action-more-btn" onclick="toggleDropdown(this)">
983
+ <svg width="16" height="16" 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>
984
+ </button>
985
+ <div class="action-dropdown">
986
+ <button class="action-dropdown-item">重启</button>
987
+ <button class="action-dropdown-item">停止</button>
988
+ <button class="action-dropdown-item danger">删除</button>
989
+ </div>
990
+ </span>
991
+ </td>
992
+ </tr>
993
+
994
+ <!-- Row 3: 创建中 -->
995
+ <tr>
996
+ <td><input type="checkbox" class="checkbox" /></td>
997
+ <td>
998
+ <div class="id-name">
999
+ <span class="id-name-name">ai-gateway-dev</span>
1000
+ <span class="id-name-id">agw-3e5f7a8b9c0d1e2f</span>
1001
+ </div>
1002
+ </td>
1003
+ <td>
1004
+ <span class="status-badge status-creating">
1005
+ <span class="status-dot"></span>
1006
+ 创建中
1007
+ </span>
1008
+ </td>
1009
+ <td>
1010
+ <div class="access-list">
1011
+ <div class="access-item">
1012
+ <span class="access-tag private">内</span>
1013
+ <span class="access-ip">172.16.2.10</span>
1014
+ <span class="access-type">内网</span>
1015
+ </div>
1016
+ </div>
1017
+ </td>
1018
+ <td><span style="font-family: var(--font-mono);">2.1.10</span></td>
1019
+ <td>dev-cluster-01</td>
1020
+ <td>default</td>
1021
+ <td>2026-05-20 10:05:00</td>
1022
+ <td class="col-action">
1023
+ <a class="action-link" href="#">详情</a>
1024
+ <span class="action-divider"></span>
1025
+ <span class="action-more">
1026
+ <button class="action-more-btn" onclick="toggleDropdown(this)">
1027
+ <svg width="16" height="16" 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>
1028
+ </button>
1029
+ <div class="action-dropdown">
1030
+ <button class="action-dropdown-item" disabled>重启</button>
1031
+ <button class="action-dropdown-item" disabled>停止</button>
1032
+ <button class="action-dropdown-item danger" disabled>删除</button>
1033
+ </div>
1034
+ </span>
1035
+ </td>
1036
+ </tr>
1037
+
1038
+ <!-- Row 4: 已停止 -->
1039
+ <tr>
1040
+ <td><input type="checkbox" class="checkbox" /></td>
1041
+ <td>
1042
+ <div class="id-name">
1043
+ <span class="id-name-name">ai-gateway-old</span>
1044
+ <span class="id-name-id">agw-9a1b2c3d4e5f6a7b</span>
1045
+ </div>
1046
+ </td>
1047
+ <td>
1048
+ <span class="status-badge status-stopped">
1049
+ <span class="status-dot"></span>
1050
+ 已停止
1051
+ </span>
1052
+ </td>
1053
+ <td>
1054
+ <div class="access-list">
1055
+ <div class="access-item">
1056
+ <span class="access-tag public">公</span>
1057
+ <span class="access-ip">47.98.55.100</span>
1058
+ <span class="access-type">公网</span>
1059
+ </div>
1060
+ </div>
1061
+ </td>
1062
+ <td><span style="font-family: var(--font-mono);">2.0.8</span></td>
1063
+ <td>prod-cluster-01</td>
1064
+ <td>ai-gateway-legacy</td>
1065
+ <td>2026-03-15 16:45:00</td>
1066
+ <td class="col-action">
1067
+ <a class="action-link" href="#">详情</a>
1068
+ <span class="action-divider"></span>
1069
+ <span class="action-more">
1070
+ <button class="action-more-btn" onclick="toggleDropdown(this)">
1071
+ <svg width="16" height="16" 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>
1072
+ </button>
1073
+ <div class="action-dropdown">
1074
+ <button class="action-dropdown-item">启动</button>
1075
+ <button class="action-dropdown-item danger">删除</button>
1076
+ </div>
1077
+ </span>
1078
+ </td>
1079
+ </tr>
1080
+
1081
+ <!-- Row 5: 异常 -->
1082
+ <tr>
1083
+ <td><input type="checkbox" class="checkbox" /></td>
1084
+ <td>
1085
+ <div class="id-name">
1086
+ <span class="id-name-name">ai-gateway-error-test</span>
1087
+ <span class="id-name-id">agw-5c6d7e8f9a0b1c2d</span>
1088
+ </div>
1089
+ </td>
1090
+ <td>
1091
+ <span class="status-badge status-error">
1092
+ <span class="status-dot"></span>
1093
+ 异常
1094
+ </span>
1095
+ </td>
1096
+ <td>
1097
+ <div class="access-list">
1098
+ <div class="access-item">
1099
+ <span class="access-tag private">内</span>
1100
+ <span class="access-ip">172.16.3.88</span>
1101
+ <span class="access-type">内网</span>
1102
+ </div>
1103
+ </div>
1104
+ </td>
1105
+ <td><span style="font-family: var(--font-mono);">2.1.11</span></td>
1106
+ <td>test-cluster-02</td>
1107
+ <td>ai-gateway</td>
1108
+ <td>2026-05-18 08:20:00</td>
1109
+ <td class="col-action">
1110
+ <a class="action-link" href="#">详情</a>
1111
+ <span class="action-divider"></span>
1112
+ <span class="action-more">
1113
+ <button class="action-more-btn" onclick="toggleDropdown(this)">
1114
+ <svg width="16" height="16" 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>
1115
+ </button>
1116
+ <div class="action-dropdown">
1117
+ <button class="action-dropdown-item">重启</button>
1118
+ <button class="action-dropdown-item danger">删除</button>
1119
+ </div>
1120
+ </span>
1121
+ </td>
1122
+ </tr>
1123
+
1124
+ <!-- Row 6: 运行中 -->
1125
+ <tr>
1126
+ <td><input type="checkbox" class="checkbox" /></td>
1127
+ <td>
1128
+ <div class="id-name">
1129
+ <span class="id-name-name">ai-gateway-multi</span>
1130
+ <span class="id-name-id">agw-1f2a3b4c5d6e7f8a</span>
1131
+ </div>
1132
+ </td>
1133
+ <td>
1134
+ <span class="status-badge status-running">
1135
+ <span class="status-dot"></span>
1136
+ 运行中
1137
+ </span>
1138
+ </td>
1139
+ <td>
1140
+ <div class="access-list">
1141
+ <div class="access-item">
1142
+ <span class="access-tag public">公</span>
1143
+ <span class="access-ip">47.98.77.200</span>
1144
+ <span class="access-type">公网</span>
1145
+ </div>
1146
+ <div class="access-item">
1147
+ <span class="access-tag private">内</span>
1148
+ <span class="access-ip">172.16.5.33</span>
1149
+ <span class="access-type">内网</span>
1150
+ </div>
1151
+ </div>
1152
+ </td>
1153
+ <td><span style="font-family: var(--font-mono);">2.1.11</span></td>
1154
+ <td>prod-cluster-02</td>
1155
+ <td>ai-gateway-multi</td>
1156
+ <td>2026-05-19 11:00:00</td>
1157
+ <td class="col-action">
1158
+ <a class="action-link" href="#">详情</a>
1159
+ <span class="action-divider"></span>
1160
+ <span class="action-more">
1161
+ <button class="action-more-btn" onclick="toggleDropdown(this)">
1162
+ <svg width="16" height="16" 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>
1163
+ </button>
1164
+ <div class="action-dropdown">
1165
+ <button class="action-dropdown-item">重启</button>
1166
+ <button class="action-dropdown-item">停止</button>
1167
+ <button class="action-dropdown-item danger">删除</button>
1168
+ </div>
1169
+ </span>
1170
+ </td>
1171
+ </tr>
1172
+ </tbody>
1173
+ </table>
1174
+ </div>
1175
+
1176
+ <!-- BulkActionBar -->
1177
+ <div class="bulk-action-bar" id="bulkActionBar">
1178
+ <div class="bulk-actions">
1179
+ <button class="btn btn-outline" style="height: 28px; font-size: 12px; padding: 0 12px;">启动</button>
1180
+ <button class="btn btn-outline" style="height: 28px; font-size: 12px; padding: 0 12px;">停止</button>
1181
+ <button class="btn btn-outline" style="height: 28px; font-size: 12px; padding: 0 12px;">重启</button>
1182
+ <button class="btn btn-outline" style="height: 28px; font-size: 12px; padding: 0 12px; color: hsl(var(--destructive)); border-color: hsl(var(--destructive-border));">删除</button>
1183
+ </div>
1184
+ <div class="bulk-stats">
1185
+ <span class="bulk-count">已选 <span id="selectedCount">0</span> 项</span>
1186
+ <span class="bulk-count">共 24 条</span>
1187
+ </div>
1188
+ </div>
1189
+
1190
+ <!-- Pagination (v7: 两段布局 — 左侧: 总条数 / 右侧: 页码导航 + 前往跳转 + 每页选择器) -->
1191
+ <div class="pagination-wrapper">
1192
+ <div>
1193
+ <span class="pagination-info">共 24 条</span>
1194
+ </div>
1195
+ <div class="pagination-right">
1196
+ <!-- 页码导航 -->
1197
+ <div class="pagination">
1198
+ <button class="pagination-btn" disabled>
1199
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
1200
+ </button>
1201
+ <button class="pagination-btn active">1</button>
1202
+ <button class="pagination-btn">2</button>
1203
+ <button class="pagination-btn">3</button>
1204
+ <button class="pagination-btn">
1205
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
1206
+ </button>
1207
+ </div>
1208
+ <!-- 前往跳转 -->
1209
+ <div class="pagination-goto">
1210
+ <span>前往</span>
1211
+ <input type="text" class="pagination-goto-input" id="gotoPageInput" maxlength="3">
1212
+ <span>页</span>
1213
+ <button class="pagination-goto-btn" id="gotoPageBtn">确定</button>
1214
+ </div>
1215
+ <!-- 每页选择器 -->
1216
+ <div class="pagination-size">
1217
+ <select>
1218
+ <option value="10" selected>10</option>
1219
+ <option value="20">20</option>
1220
+ <option value="50">50</option>
1221
+ </select>
1222
+ </div>
1223
+ </div>
1224
+ </div>
1225
+
1226
+ </div>
1227
+ </div>
1228
+ </div>
1229
+ </div>
1230
+ </div>
1231
+
1232
+ <script>
1233
+ // SearchCombo
1234
+ document.getElementById('searchBtn').addEventListener('click', function() {
1235
+ console.log('Search: ' + document.getElementById('searchDimension').value + ' - ' + document.getElementById('searchInput').value);
1236
+ });
1237
+ document.getElementById('searchInput').addEventListener('keydown', function(e) {
1238
+ if (e.key === 'Enter') {
1239
+ console.log('Search on Enter: ' + document.getElementById('searchDimension').value + ' - ' + this.value);
1240
+ }
1241
+ });
1242
+ document.getElementById('searchDimension').addEventListener('change', function() {
1243
+ document.getElementById('searchInput').value = '';
1244
+ document.getElementById('searchInput').focus();
1245
+ });
1246
+
1247
+ // Action dropdown
1248
+ function toggleDropdown(btn) {
1249
+ const dropdown = btn.nextElementSibling;
1250
+ const currentRow = btn.closest('tr');
1251
+ // Close all other dropdowns & remove row elevation
1252
+ document.querySelectorAll('.action-dropdown.show').forEach(d => {
1253
+ if (d !== dropdown) {
1254
+ d.classList.remove('show');
1255
+ const row = d.closest('tr');
1256
+ if (row) row.classList.remove('has-dropdown-open');
1257
+ }
1258
+ });
1259
+ dropdown.classList.toggle('show');
1260
+ if (currentRow) currentRow.classList.toggle('has-dropdown-open', dropdown.classList.contains('show'));
1261
+ }
1262
+ document.addEventListener('click', function(e) {
1263
+ if (!e.target.closest('.action-more')) {
1264
+ document.querySelectorAll('.action-dropdown.show').forEach(d => {
1265
+ d.classList.remove('show');
1266
+ const row = d.closest('tr');
1267
+ if (row) row.classList.remove('has-dropdown-open');
1268
+ });
1269
+ }
1270
+ });
1271
+
1272
+ // Sidebar collapse toggle
1273
+ const sidebar = document.querySelector('.sidebar');
1274
+ document.querySelector('.sidebar-collapse-btn').addEventListener('click', function() {
1275
+ sidebar.classList.toggle('collapsed');
1276
+ });
1277
+ // Sidebar expand via logo click (collapsed state)
1278
+ document.getElementById('sidebarLogoArea').addEventListener('click', function() {
1279
+ if (sidebar.classList.contains('collapsed')) {
1280
+ sidebar.classList.remove('collapsed');
1281
+ }
1282
+ });
1283
+
1284
+ // Sidebar expand toggle
1285
+ document.querySelectorAll('.sidebar-menu-item[data-toggle]').forEach(item => {
1286
+ item.addEventListener('click', function(e) {
1287
+ e.preventDefault();
1288
+ this.classList.toggle('expanded');
1289
+ const targetId = this.getAttribute('data-toggle');
1290
+ const target = document.getElementById(targetId);
1291
+ if (target) target.classList.toggle('show');
1292
+ });
1293
+ });
1294
+
1295
+ // Bulk selection
1296
+ const selectAll = document.getElementById('selectAll');
1297
+ const checkboxes = document.querySelectorAll('#tableBody .checkbox');
1298
+ const bulkActionBar = document.getElementById('bulkActionBar');
1299
+ const selectedCount = document.getElementById('selectedCount');
1300
+
1301
+ selectAll.addEventListener('change', function() {
1302
+ checkboxes.forEach(cb => { cb.checked = this.checked; });
1303
+ updateBulkActionBar();
1304
+ });
1305
+
1306
+ checkboxes.forEach(cb => {
1307
+ cb.addEventListener('change', updateBulkActionBar);
1308
+ });
1309
+
1310
+ function updateBulkActionBar() {
1311
+ const count = Array.from(checkboxes).filter(cb => cb.checked).length;
1312
+ selectedCount.textContent = count;
1313
+ if (count > 0) {
1314
+ bulkActionBar.classList.add('show');
1315
+ } else {
1316
+ bulkActionBar.classList.remove('show');
1317
+ }
1318
+ // Update selectAll state
1319
+ const allChecked = Array.from(checkboxes).every(cb => cb.checked);
1320
+ const someChecked = Array.from(checkboxes).some(cb => cb.checked);
1321
+ selectAll.checked = allChecked;
1322
+ selectAll.indeterminate = someChecked && !allChecked;
1323
+ }
1324
+
1325
+ // Sticky header scroll
1326
+ const cardScrollable = document.querySelector('.card-scrollable');
1327
+ const stickyWrapper = document.querySelector('.sticky-wrapper');
1328
+ cardScrollable.addEventListener('scroll', function() {
1329
+ if (this.scrollTop > 0) {
1330
+ stickyWrapper.style.background = 'hsl(var(--card))';
1331
+ } else {
1332
+ stickyWrapper.style.background = 'transparent';
1333
+ }
1334
+ });
1335
+
1336
+ // Pagination
1337
+ document.querySelectorAll('.pagination-btn:not(:disabled)').forEach(btn => {
1338
+ btn.addEventListener('click', function() {
1339
+ document.querySelectorAll('.pagination-btn.active').forEach(b => b.classList.remove('active'));
1340
+ this.classList.add('active');
1341
+ });
1342
+ });
1343
+
1344
+ // Pagination Goto
1345
+ const gotoInput = document.getElementById('gotoPageInput');
1346
+ const gotoBtn = document.getElementById('gotoPageBtn');
1347
+ function doGotoPage() {
1348
+ var page = parseInt(gotoInput.value, 10);
1349
+ if (!isNaN(page) && page >= 1) {
1350
+ console.log('跳转到第', page, '页');
1351
+ gotoInput.value = '';
1352
+ }
1353
+ }
1354
+ gotoBtn.addEventListener('click', doGotoPage);
1355
+ gotoInput.addEventListener('keydown', function(e) {
1356
+ if (e.key === 'Enter') doGotoPage();
1357
+ });
1358
+ </script>
1359
+
1360
+ </body>
1361
+ </html>