@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,877 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=1400">
6
+ <title>向导表单页 · 标准参考范例 — v7.8.4</title>
7
+ <!--
8
+ ★ 向导表单页标准参考范例(Standard Wizard Form Reference)
9
+ ─────────────────────────────────────────────────────────────
10
+ 用途:AI 生成多步骤向导表单页时的基础参考,展示步骤导航 + 分步表单骨架
11
+ ─────────────────────────────────────────────────────────────
12
+ 布局:TWO_COL(Sidebar + ContentCard 单例容器)
13
+ Sidebar:完整 L1 框架(折叠/展开 240px↔68px / flyout / sub-menu)
14
+ 内容区模式:D-WIZARD(wizard-form 向导表单)
15
+ ─────────────────────────────────────────────────────────────
16
+ 组件清单:
17
+ • Breadcrumb — 2级面包屑(L2 模式)
18
+ • Steps — 横向步骤条(编号圆圈 + 连接线 + 步骤标签)
19
+ • Wizard Step Content — 分步表单(每步独立 section + form-rows)
20
+ • Form Controls — Input / Select / Stepper / Switch
21
+ • Wizard Footer — 动态底栏(取消 / 上一步 / 下一步 / 提交)
22
+ ─────────────────────────────────────────────────────────────
23
+ 规范来源:pages/form-page/SKILL.md
24
+ Tokens 版本:v7.8.4
25
+ -->
26
+ <style>
27
+ /* ===== Reset & v7 Design Tokens ===== */
28
+ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
29
+ :root {
30
+ /* Primary */
31
+ --primary: 218.6 100% 46.7%;
32
+ --brand-primary: 213 94% 48%;
33
+ --primary-foreground: 0 0% 100%;
34
+ --primary-hover: 218.6 100% 42%;
35
+
36
+ /* Gray (v7.4) */
37
+ --gray-primary: 220 9% 7%;
38
+ --gray-primary-foreground: 210 33% 99%;
39
+ --gray-secondary-foreground: 216 4% 26%;
40
+ --muted-foreground: 216 2% 45%;
41
+ --gray-placeholder: 216 2% 45%;
42
+ --gray-disabled: 210 3% 63%;
43
+ --gray-line: 210 9% 91%;
44
+ --gray-sidebar-accent: 210 12% 93%;
45
+ --gray-muted: 220 18% 97%;
46
+ --gray-white: 0 0% 100%;
47
+ /* Legacy 别名(v7.4 向后兼容) */
48
+ --gray-title: 220 9% 7%;
49
+ --gray-secondary: 216 4% 26%;
50
+ --gray-tertiary: 216 2% 45%;
51
+ --gray-border: 210 9% 91%;
52
+ --gray-fill: 220 18% 97%;
53
+ --gray-bg: 220 18% 97%;
54
+
55
+ /* Feedback */
56
+ --success: 142 76% 36%;
57
+ --success-bg: 145 65% 95%;
58
+ --destructive: 0 72% 51%;
59
+
60
+ /* Semantic */
61
+ --background: 220 18% 97%;
62
+ --page-bg: 220 18% 97%;
63
+ --card: 0 0% 100%;
64
+ --ring: 218.6 100% 46.7%;
65
+ --sidebar: 220 18% 97%;
66
+ --sidebar-foreground: 220 9% 7%;
67
+ --sidebar-active: 210 12% 93%;
68
+ --sidebar-active-foreground: 220 9% 7%;
69
+ --sidebar-hover: 220 18% 97%;
70
+ --sidebar-group: 216 2% 45%;
71
+ --sidebar-item: 220 9% 7%;
72
+ --sidebar-item-muted: 216 4% 26%;
73
+ --sidebar-border: 210 9% 91%;
74
+
75
+ /* Radius */
76
+ --radius-sm: 4px;
77
+ --radius-md: 8px;
78
+ --radius: 12px;
79
+ --radius-lg: 16px;
80
+
81
+ /* Spacing */
82
+ --gap-xs: 4px;
83
+ --gap-sm: 8px;
84
+ --gap-md: 10px;
85
+ --gap-lg: 12px;
86
+ --gap-xl: 20px;
87
+ --gap-2xl: 24px;
88
+ --btn-padding-x: 16px;
89
+ --btn-padding-x-sm: 12px;
90
+ --button-gap: 8px;
91
+ --tabs-gap: 24px;
92
+
93
+ /* Typography */
94
+ --font-size-xs: 12px;
95
+ --font-size-base: 12px;
96
+ --font-size-sm: 13px;
97
+ --font-size-lg: 14px;
98
+ --font-size-xl: 16px;
99
+ --font-size-2xl: 18px;
100
+ --font-size-page-header: 18px;
101
+ --font-sans: 'PingFang SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
102
+ --font-mono: 'SF Mono', Monaco, 'Courier New', monospace;
103
+ --font-weight-normal: 400;
104
+ --font-weight-medium: 500;
105
+ --font-weight-semibold: 600;
106
+ --font-weight-black: 900;
107
+
108
+ /* Shadow */
109
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
110
+
111
+ /* Layout */
112
+ --layout-sidebar-width: 240px;
113
+ --sidebar-collapsed-width: 68px;
114
+ --page-container-padding: 16px;
115
+ --page-container-padding-left: 0px;
116
+ --card-padding-x: 20px;
117
+ --card-gap: 16px;
118
+
119
+ /* Transitions */
120
+ --duration-fast: 150ms;
121
+ --duration-normal: 200ms;
122
+ --easing-ease: ease;
123
+ }
124
+
125
+ body {
126
+ font-family: var(--font-sans);
127
+ font-size: var(--font-size-lg);
128
+ color: hsl(var(--gray-primary));
129
+ background: hsl(var(--background));
130
+ line-height: 1.5;
131
+ }
132
+
133
+ /* ===== Sidebar ===== */
134
+ .sidebar {
135
+ position: fixed;
136
+ top: 0; left: 0; bottom: 0;
137
+ width: var(--layout-sidebar-width);
138
+ background: hsl(var(--sidebar));
139
+ z-index: 100;
140
+ display: flex;
141
+ flex-direction: column;
142
+ overflow: hidden;
143
+ transition: width 0.2s ease;
144
+ }
145
+ .sidebar-header {
146
+ height: 56px; padding: 0 16px;
147
+ display: flex; align-items: center; gap: var(--gap-xs);
148
+ flex-shrink: 0;
149
+ }
150
+ .sidebar-logo {
151
+ height: 28px; flex-shrink: 0;
152
+ display: flex; align-items: center;
153
+ overflow: hidden;
154
+ }
155
+ .sidebar-logo img { height: 28px; width: auto; display: block; }
156
+ .sidebar-logo .collapsed-logo { display: none; width: 30px; height: 30px; }
157
+ .sidebar-logo .expand-logo { display: none; width: 18px; height: 18px; color: hsl(var(--muted-foreground)); }
158
+ .sidebar-collapse-btn {
159
+ margin-left: auto; width: 18px; height: 18px;
160
+ background: none; border: none; padding: 0; cursor: pointer;
161
+ display: flex; align-items: center; justify-content: center;
162
+ color: hsl(var(--muted-foreground)); flex-shrink: 0;
163
+ }
164
+ .sidebar-collapse-btn svg { width: 18px; height: 18px; }
165
+ .sidebar-content { flex: 1; overflow-y: auto; padding: 12px 0; }
166
+ .sidebar-menu { list-style: none; padding: 0 8px; display: flex; flex-direction: column; gap: 2px; }
167
+ .sidebar-menu-item {
168
+ display: flex; align-items: center; gap: var(--button-gap);
169
+ padding: 0 12px; height: 40px; border-radius: var(--radius-md);
170
+ font-size: var(--font-size-lg); font-weight: var(--font-weight-medium);
171
+ color: hsl(var(--sidebar-item-muted)); text-decoration: none;
172
+ cursor: pointer; transition: background var(--duration-fast), color var(--duration-fast);
173
+ white-space: nowrap;
174
+ }
175
+ .sidebar-menu-item:hover { background: hsl(var(--sidebar-hover)); color: hsl(var(--sidebar-item)); }
176
+ .sidebar-menu-item.active { background: hsl(var(--sidebar-active)); color: hsl(var(--sidebar-active-foreground)); }
177
+ .sidebar-menu-item .expand-icon { margin-left: auto; width: 16px; height: 16px; transition: transform var(--duration-fast); }
178
+ .sidebar-menu-item.expanded .expand-icon { transform: rotate(90deg); }
179
+ .sidebar-sub-menu { list-style: none; padding: 0 0 0 24px; display: none; }
180
+ .sidebar-menu > li > .sidebar-sub-menu { padding-left: 0; }
181
+ .sidebar-sub-menu.show { display: block; }
182
+ .sidebar-sub-menu .sidebar-menu-item { padding-left: 36px; height: 36px; font-size: var(--font-size-base); font-weight: var(--font-weight-normal); }
183
+ .item-icon { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
184
+ .item-icon svg { width: 16px; height: 16px; }
185
+
186
+ /* Sidebar Collapsed */
187
+ .sidebar.collapsed { width: 68px; overflow: visible; }
188
+ .sidebar.collapsed .sidebar-header { padding: 0 12px; justify-content: center; }
189
+ .sidebar.collapsed .sidebar-logo { cursor: pointer; }
190
+ .sidebar.collapsed .sidebar-logo img { display: none; }
191
+ .sidebar.collapsed .sidebar-logo .collapsed-logo { display: block; }
192
+ .sidebar.collapsed .sidebar-logo:hover .collapsed-logo { display: none; }
193
+ .sidebar.collapsed .sidebar-logo:hover .expand-logo { display: block; }
194
+ .sidebar.collapsed .sidebar-collapse-btn { display: none; }
195
+ .sidebar.collapsed .sidebar-content { padding: 12px 0; overflow-y: auto; overflow-x: hidden; }
196
+ .sidebar.collapsed .sidebar-menu { padding: 0; }
197
+ .sidebar.collapsed .sidebar-menu > li { display: flex; justify-content: center; }
198
+ .sidebar.collapsed .sidebar-menu > li > a.sidebar-menu-item {
199
+ justify-content: center; padding: 0; width: 40px; height: 40px;
200
+ border-radius: var(--radius-md); position: relative; flex-shrink: 0; gap: 0;
201
+ }
202
+ .sidebar.collapsed .sidebar-menu > li > a.sidebar-menu-item .item-icon { margin: 0; }
203
+ .sidebar.collapsed .sidebar-menu > li > a.sidebar-menu-item > span:not(.item-icon),
204
+ .sidebar.collapsed .sidebar-menu > li > a.sidebar-menu-item > .expand-icon { display: none; }
205
+ .sidebar.collapsed .sidebar-menu > li > a { font-size: 0; }
206
+ .sidebar.collapsed .sidebar-menu > li > a > .item-icon { font-size: var(--font-size-lg); }
207
+ .sidebar.collapsed .sidebar-sub-menu { display: none !important; }
208
+ .sidebar.collapsed .sidebar-menu > li { position: relative; }
209
+ .sidebar.collapsed .sidebar-menu > li:hover > .sidebar-flyout { display: block; }
210
+ .sidebar-flyout {
211
+ display: none; position: absolute; left: 100%; top: 0;
212
+ min-width: 160px; padding: 4px;
213
+ background: hsl(var(--card)); border: 1px solid hsl(var(--gray-line));
214
+ border-radius: var(--radius-md); box-shadow: 0 4px 12px rgba(0,0,0,0.08); z-index: 200;
215
+ }
216
+ .sidebar-flyout .sidebar-menu-item {
217
+ height: 36px; padding: 0 12px; font-size: var(--font-size-base) !important;
218
+ font-weight: var(--font-weight-normal); color: hsl(var(--gray-primary));
219
+ border-radius: var(--radius-sm); white-space: nowrap;
220
+ }
221
+ .sidebar-flyout .sidebar-menu-item:hover { background: hsl(var(--gray-muted)); }
222
+ .sidebar-divider { height: 1px; background: hsl(var(--sidebar-border)); margin: 0 12px; flex-shrink: 0; }
223
+ .sidebar-footer { flex-shrink: 0; display: flex; align-items: center; gap: 8px; padding: 12px 12px 16px 24px; }
224
+ .sidebar-avatar {
225
+ width: 28px; height: 28px; border-radius: 50%;
226
+ background: hsl(var(--sidebar-active)); display: flex; align-items: center; justify-content: center;
227
+ font-size: var(--font-size-base); font-weight: 600; color: hsl(var(--gray-primary)); flex-shrink: 0;
228
+ }
229
+ .sidebar-username { flex: 1; font-size: var(--font-size-lg); font-weight: var(--font-weight-medium); color: hsl(var(--sidebar-item)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
230
+ .sidebar-more { width: 28px; height: 28px; border-radius: var(--radius-sm); border: none; background: transparent; cursor: pointer; display: flex; align-items: center; justify-content: center; color: hsl(var(--muted-foreground)); }
231
+ .sidebar-more:hover { background: hsl(var(--sidebar-hover)); color: hsl(var(--gray-primary)); }
232
+ .sidebar.collapsed .sidebar-divider { margin: 0 14px; }
233
+ .sidebar.collapsed .sidebar-footer { padding: 12px 10px 16px; justify-content: center; }
234
+ .sidebar.collapsed .sidebar-username, .sidebar.collapsed .sidebar-more { display: none; }
235
+
236
+ /* ===== PageContainer ===== */
237
+ .page-container {
238
+ margin-left: var(--layout-sidebar-width);
239
+ padding: var(--page-container-padding) var(--page-container-padding) var(--page-container-padding) var(--page-container-padding-left);
240
+ min-height: 100vh;
241
+ transition: margin-left 0.2s ease;
242
+ }
243
+ .sidebar.collapsed ~ .page-container { margin-left: 68px; }
244
+
245
+ /* ===== Breadcrumb ===== */
246
+ .breadcrumb {
247
+ display: flex; align-items: center; gap: 4px;
248
+ font-size: var(--font-size-2xl); font-weight: var(--font-weight-medium);
249
+ color: hsl(var(--gray-secondary-foreground)); padding: 0; height: 64px;
250
+ flex-shrink: 0; margin-bottom: 0;
251
+ }
252
+ .breadcrumb a { color: hsl(var(--gray-secondary-foreground)); text-decoration: none; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
253
+ .breadcrumb a:hover { color: hsl(var(--gray-primary)); }
254
+ .breadcrumb-sep { display: flex; align-items: center; color: hsl(var(--muted-foreground)); }
255
+ .breadcrumb-sep svg { width: 20px; height: 20px; }
256
+ .breadcrumb-current { color: hsl(var(--gray-primary)); font-weight: var(--font-weight-medium); cursor: default; }
257
+
258
+ /* ===== Card ===== */
259
+ .card {
260
+ background: hsl(var(--card)); border-radius: var(--radius-lg);
261
+ /* v7.6:去掉 1px border,使用最弱阴影 var(--shadow-sm) */
262
+ box-shadow: var(--shadow-sm, 0 1px 2px rgba(0,0,0,0.05));
263
+ overflow: hidden; display: flex; flex-direction: column;
264
+ max-height: calc(100vh - 48px); padding-left: var(--card-padding-x); padding-right: var(--card-padding-x);
265
+ }
266
+ .card-scrollable { overflow-y: auto; flex: 1 1 auto; min-height: 0; }
267
+
268
+ /* ===== Steps Component ===== */
269
+ .steps-wrapper {
270
+ padding: var(--gap-2xl) 0;
271
+ flex-shrink: 0;
272
+ }
273
+ .steps {
274
+ display: flex;
275
+ align-items: center;
276
+ justify-content: center;
277
+ max-width: 600px;
278
+ margin: 0 auto;
279
+ }
280
+ .step-item {
281
+ display: flex;
282
+ align-items: center;
283
+ flex: 1;
284
+ position: relative;
285
+ }
286
+ .step-item:last-child { flex: 0 0 auto; }
287
+ .step-indicator {
288
+ display: flex;
289
+ align-items: center;
290
+ gap: var(--gap-sm);
291
+ white-space: nowrap;
292
+ position: relative;
293
+ z-index: 1;
294
+ }
295
+ .step-circle {
296
+ width: 30px; height: 30px;
297
+ border-radius: 50%;
298
+ display: flex; align-items: center; justify-content: center;
299
+ font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold);
300
+ flex-shrink: 0;
301
+ transition: all var(--duration-normal);
302
+ border: 2px solid hsl(var(--gray-line));
303
+ background: hsl(var(--card));
304
+ color: hsl(var(--gray-secondary-foreground));
305
+ }
306
+ .step-item:focus { outline: none; }
307
+ .step-item:focus-visible .step-circle {
308
+ box-shadow: 0 0 0 2px hsl(var(--ring) / 0.35);
309
+ }
310
+ .step-item.active .step-circle {
311
+ border-color: hsl(var(--brand-primary));
312
+ background: hsl(var(--brand-primary));
313
+ color: hsl(var(--primary-foreground));
314
+ }
315
+ .step-item.completed .step-circle {
316
+ border-color: hsl(var(--brand-primary));
317
+ background: hsl(var(--brand-primary));
318
+ color: hsl(var(--primary-foreground));
319
+ }
320
+ .step-label {
321
+ font-size: var(--font-size-lg);
322
+ color: hsl(var(--gray-secondary-foreground));
323
+ font-weight: var(--font-weight-normal);
324
+ transition: all var(--duration-normal);
325
+ }
326
+ .step-item.active .step-label {
327
+ color: hsl(var(--gray-primary));
328
+ font-weight: var(--font-weight-semibold);
329
+ }
330
+ .step-item.completed .step-label {
331
+ color: hsl(var(--gray-primary));
332
+ font-weight: var(--font-weight-medium);
333
+ }
334
+ .step-connector {
335
+ flex: 1;
336
+ height: 2px;
337
+ background: hsl(var(--gray-line));
338
+ margin: 0 var(--gap-lg);
339
+ border-radius: 9999px;
340
+ transition: background var(--duration-normal);
341
+ }
342
+ .step-item.completed .step-connector {
343
+ background: hsl(var(--brand-primary));
344
+ }
345
+
346
+ /* ===== Form Section ===== */
347
+ .wizard-step-content {
348
+ display: none;
349
+ padding: var(--gap-2xl) 0;
350
+ }
351
+ .wizard-step-content.active { display: block; }
352
+ .section-title {
353
+ font-size: var(--font-size-xl); font-weight: var(--font-weight-semibold);
354
+ color: hsl(var(--gray-primary)); margin-bottom: var(--gap-sm);
355
+ }
356
+ .section-desc {
357
+ font-size: var(--font-size-sm); color: hsl(var(--gray-secondary-foreground));
358
+ margin-bottom: var(--gap-xl);
359
+ }
360
+
361
+ /* ===== Form Row ===== */
362
+ .form-row { display: flex; align-items: center; gap: var(--gap-2xl); padding: 12px 0; }
363
+ .form-label {
364
+ width: 140px; flex-shrink: 0;
365
+ font-size: var(--font-size-lg); font-weight: var(--font-weight-medium);
366
+ color: hsl(var(--gray-primary));
367
+ }
368
+ .form-label .required { color: hsl(var(--destructive)); margin-left: 2px; }
369
+ .form-control { flex: 1; min-width: 0; max-width: 400px; }
370
+
371
+ /* ===== Input ===== */
372
+ .input {
373
+ height: 32px; padding: 0 8px;
374
+ border: 1px solid hsl(var(--gray-line)); border-radius: var(--radius-md);
375
+ font-size: var(--font-size-base); color: hsl(var(--gray-primary));
376
+ background: hsl(var(--card)); outline: none;
377
+ transition: border-color var(--duration-fast), box-shadow var(--duration-fast);
378
+ width: 100%;
379
+ }
380
+ .input:focus { border-color: hsl(var(--primary)); box-shadow: 0 0 0 2px hsl(var(--ring) / 0.2); }
381
+ .input::placeholder { color: hsl(var(--gray-placeholder)); }
382
+
383
+ /* ===== Select ===== */
384
+ .select-wrapper { position: relative; width: 100%; }
385
+ .select-trigger {
386
+ height: 32px; padding: 0 8px; padding-right: 28px;
387
+ border: 1px solid hsl(var(--gray-line)); border-radius: var(--radius-md);
388
+ background: hsl(var(--card)); font-size: var(--font-size-base);
389
+ color: hsl(var(--gray-primary)); display: flex; align-items: center;
390
+ width: 100%; cursor: pointer; outline: none;
391
+ transition: border-color var(--duration-fast), box-shadow var(--duration-fast);
392
+ position: relative;
393
+ }
394
+ .select-trigger:focus { border-color: hsl(var(--primary)); box-shadow: 0 0 0 2px hsl(var(--ring) / 0.2); }
395
+ .select-trigger::after {
396
+ content: ''; position: absolute; right: 7px; top: 50%; transform: translateY(-50%);
397
+ width: 14px; height: 14px;
398
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%231A1A1A' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' opacity='0.8'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
399
+ background-size: contain; background-repeat: no-repeat; pointer-events: none;
400
+ }
401
+ .select-trigger.placeholder { color: hsl(var(--gray-placeholder)); }
402
+ .select-content {
403
+ display: none; position: absolute; z-index: 50; top: calc(100% + 4px);
404
+ left: 0; right: 0; background: hsl(var(--card));
405
+ border: 1px solid hsl(var(--gray-line)); border-radius: var(--radius-md);
406
+ box-shadow: 0 6px 24px rgba(0,0,0,0.1); padding: 8px; min-width: 100%;
407
+ }
408
+ .select-content.show { display: block; }
409
+ .select-item {
410
+ height: 32px; padding: 0 8px; border-radius: var(--radius-sm);
411
+ font-size: var(--font-size-base); color: hsl(var(--gray-primary));
412
+ display: flex; align-items: center; cursor: pointer; user-select: none;
413
+ }
414
+ .select-item:hover { background: hsl(var(--gray-muted)); }
415
+ .select-item.selected { color: hsl(var(--primary)); font-weight: var(--font-weight-medium); }
416
+
417
+ /* ===== Switch ===== */
418
+ .switch { position: relative; width: 28px; height: 16px; cursor: pointer; }
419
+ .switch input { display: none; }
420
+ .switch-track { position: absolute; inset: 0; background: hsl(var(--gray-line)); border-radius: 8px; transition: background var(--duration-fast); }
421
+ .switch input:checked + .switch-track { background: hsl(var(--success)); }
422
+ .switch-thumb { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; background: white; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,0.15); transition: transform var(--duration-fast); pointer-events: none; }
423
+ .switch input:checked ~ .switch-thumb { transform: translateX(12px); }
424
+
425
+ /* ===== Stepper ===== */
426
+ .stepper { display: flex; align-items: center; gap: 0; }
427
+ .stepper-btn {
428
+ width: 32px; height: 36px;
429
+ border: 1px solid hsl(var(--gray-line)); background: hsl(var(--card));
430
+ cursor: pointer; display: flex; align-items: center; justify-content: center;
431
+ font-size: 16px; color: hsl(var(--gray-primary));
432
+ }
433
+ .stepper-btn:first-child { border-radius: var(--radius-md) 0 0 var(--radius-md); }
434
+ .stepper-btn:last-child { border-radius: 0 var(--radius-md) var(--radius-md) 0; }
435
+ .stepper input {
436
+ width: 60px; height: 36px; text-align: center;
437
+ border: 1px solid hsl(var(--gray-line)); border-left: none; border-right: none;
438
+ font-size: var(--font-size-lg); color: hsl(var(--gray-primary)); outline: none;
439
+ }
440
+
441
+ /* ===== Buttons ===== */
442
+ .btn {
443
+ display: inline-flex; align-items: center; justify-content: center;
444
+ height: 36px; padding: 0 var(--btn-padding-x); border-radius: var(--radius-md);
445
+ font-size: var(--font-size-lg); font-weight: var(--font-weight-medium);
446
+ cursor: pointer; border: 1px solid transparent;
447
+ transition: all var(--duration-fast); white-space: nowrap; gap: 6px;
448
+ }
449
+ .btn-primary { background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
450
+ .btn-primary:hover { background: hsl(var(--primary-hover)); }
451
+ .btn-secondary { background: hsl(var(--card)); color: hsl(var(--gray-primary)); border-color: hsl(var(--gray-line)); }
452
+ .btn-secondary:hover { background: hsl(var(--gray-muted)); }
453
+ .btn-link { background: transparent; color: hsl(var(--gray-secondary-foreground)); border: none; padding: 0 8px; }
454
+ .btn-link:hover { color: hsl(var(--gray-primary)); text-decoration: underline; }
455
+
456
+ /* ===== Footer ===== */
457
+ .settings-footer {
458
+ display: flex; align-items: center; justify-content: flex-end;
459
+ height: 72px; padding: 0; gap: var(--gap-sm);
460
+ border-top: 1px solid hsl(var(--gray-line)); background: hsl(var(--card));
461
+ flex-shrink: 0; z-index: 10;
462
+ margin-left: calc(-1 * var(--card-padding-x)); margin-right: calc(-1 * var(--card-padding-x)); padding-left: var(--card-padding-x); padding-right: var(--card-padding-x);
463
+ }
464
+
465
+ /* ===== Form Description ===== */
466
+ .form-description { font-size: var(--font-size-sm); color: hsl(var(--gray-secondary-foreground)); margin-top: 4px; }
467
+ </style>
468
+ </head>
469
+ <body>
470
+
471
+ <!-- ===== Sidebar ===== -->
472
+ <aside class="sidebar">
473
+ <div class="sidebar-header">
474
+ <div class="sidebar-logo" id="sidebarLogoArea">
475
+ <img src="./_assets/op-ai-gateway-logo.svg" alt="OP AI Gateway" />
476
+ <img class="collapsed-logo" src="./_assets/op-logo-collapsed.png" alt="OP" />
477
+ <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>
478
+ </div>
479
+ <button class="sidebar-collapse-btn" title="收起侧边栏">
480
+ <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>
481
+ </button>
482
+ </div>
483
+
484
+ <div class="sidebar-content">
485
+ <ul class="sidebar-menu">
486
+ <li><a class="sidebar-menu-item active" href="#">
487
+ <span class="item-icon"><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></span>
488
+ AI 网关
489
+ </a></li>
490
+ <li>
491
+ <a class="sidebar-menu-item expanded" href="#" data-toggle="api-gateway">
492
+ <span class="item-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg></span>
493
+ API 网关
494
+ <span class="expand-icon"><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></span>
495
+ </a>
496
+ <ul class="sidebar-sub-menu show" id="api-gateway">
497
+ <li><a class="sidebar-menu-item" href="#">实例管理</a></li>
498
+ <li><a class="sidebar-menu-item" href="#">共享实例管理</a></li>
499
+ </ul>
500
+ <div class="sidebar-flyout">
501
+ <a class="sidebar-menu-item" href="#">实例管理</a>
502
+ <a class="sidebar-menu-item" href="#">共享实例管理</a>
503
+ </div>
504
+ </li>
505
+ <li>
506
+ <a class="sidebar-menu-item" href="#" data-toggle="cloud-native-gw">
507
+ <span class="item-icon"><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></span>
508
+ 云原生网关
509
+ <span class="expand-icon"><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></span>
510
+ </a>
511
+ <ul class="sidebar-sub-menu" id="cloud-native-gw">
512
+ <li><a class="sidebar-menu-item" href="#">实例列表</a></li>
513
+ <li><a class="sidebar-menu-item" href="#">Nginx Ingress 迁移</a></li>
514
+ </ul>
515
+ <div class="sidebar-flyout">
516
+ <a class="sidebar-menu-item" href="#">实例列表</a>
517
+ <a class="sidebar-menu-item" href="#">Nginx Ingress 迁移</a>
518
+ </div>
519
+ </li>
520
+ <li><a class="sidebar-menu-item" href="#">
521
+ <span class="item-icon"><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></span>
522
+ 集群管理
523
+ </a></li>
524
+ </ul>
525
+ </div>
526
+
527
+ <div class="sidebar-divider"></div>
528
+ <div class="sidebar-footer">
529
+ <div class="sidebar-avatar">A</div>
530
+ <span class="sidebar-username">admin</span>
531
+ <button class="sidebar-more">
532
+ <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>
533
+ </button>
534
+ </div>
535
+ </aside>
536
+
537
+ <!-- ===== PageContainer ===== -->
538
+ <div class="page-container">
539
+ <div class="content-wrapper">
540
+ <div class="card">
541
+ <div class="card-scrollable">
542
+
543
+ <!-- Breadcrumb -->
544
+ <nav class="breadcrumb">
545
+ <a href="#">AI 网关</a>
546
+ <span class="breadcrumb-sep"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg></span>
547
+ <span class="breadcrumb-current">创建实例</span>
548
+ </nav>
549
+
550
+ <!-- Steps -->
551
+ <div class="steps-wrapper" role="group" aria-label="创建实例步骤">
552
+ <div class="steps" role="tablist" aria-orientation="horizontal">
553
+ <div class="step-item active" data-step="1" role="tab" aria-selected="true" tabindex="0">
554
+ <div class="step-indicator">
555
+ <div class="step-circle" aria-label="第 1 步:基本信息">1</div>
556
+ <span class="step-label">基本信息</span>
557
+ </div>
558
+ <div class="step-connector" aria-hidden="true"></div>
559
+ </div>
560
+ <div class="step-item" data-step="2" role="tab" aria-selected="false" tabindex="-1">
561
+ <div class="step-indicator">
562
+ <div class="step-circle" aria-label="第 2 步:集群配置">2</div>
563
+ <span class="step-label">集群配置</span>
564
+ </div>
565
+ <div class="step-connector" aria-hidden="true"></div>
566
+ </div>
567
+ <div class="step-item" data-step="3" role="tab" aria-selected="false" tabindex="-1">
568
+ <div class="step-indicator">
569
+ <div class="step-circle" aria-label="第 3 步:可观测性配置">3</div>
570
+ <span class="step-label">可观测性配置</span>
571
+ </div>
572
+ </div>
573
+ </div>
574
+ </div>
575
+
576
+ <!-- Step 1: 基本信息 -->
577
+ <div class="wizard-step-content active" id="step-1">
578
+ <h2 class="section-title">基本信息</h2>
579
+ <p class="section-desc">配置实例的基本属性</p>
580
+
581
+ <div class="form-row">
582
+ <div class="form-label">实例类型<span class="required">*</span></div>
583
+ <div class="form-control">
584
+ <div class="select-wrapper">
585
+ <button type="button" class="select-trigger" data-value="AI网关">AI网关</button>
586
+ <div class="select-content">
587
+ <div class="select-item selected" data-value="AI网关">AI网关</div>
588
+ <div class="select-item" data-value="API网关">API网关</div>
589
+ </div>
590
+ </div>
591
+ </div>
592
+ </div>
593
+
594
+ <div class="form-row">
595
+ <div class="form-label">实例名称<span class="required">*</span></div>
596
+ <div class="form-control">
597
+ <input class="input" type="text" placeholder="请输入实例名称(小写字母、数字、连字符)">
598
+ </div>
599
+ </div>
600
+
601
+ <div class="form-row">
602
+ <div class="form-label">实例规格<span class="required">*</span></div>
603
+ <div class="form-control">
604
+ <div class="select-wrapper">
605
+ <button type="button" class="select-trigger" data-value="mini">mini</button>
606
+ <div class="select-content">
607
+ <div class="select-item selected" data-value="mini">mini</div>
608
+ <div class="select-item" data-value="small">small</div>
609
+ <div class="select-item" data-value="standard">standard</div>
610
+ <div class="select-item" data-value="large">large</div>
611
+ </div>
612
+ </div>
613
+ </div>
614
+ </div>
615
+
616
+ <div class="form-row">
617
+ <div class="form-label">实例节点数<span class="required">*</span></div>
618
+ <div class="form-control">
619
+ <div class="stepper">
620
+ <button class="stepper-btn">-</button><input type="number" value="1" min="1"><button class="stepper-btn">+</button>
621
+ </div>
622
+ </div>
623
+ </div>
624
+ </div>
625
+
626
+ <!-- Step 2: 集群配置 -->
627
+ <div class="wizard-step-content" id="step-2">
628
+ <h2 class="section-title">集群配置</h2>
629
+ <p class="section-desc">选择实例部署的集群和命名空间</p>
630
+
631
+ <div class="form-row">
632
+ <div class="form-label">集群<span class="required">*</span></div>
633
+ <div class="form-control">
634
+ <div class="select-wrapper">
635
+ <button type="button" class="select-trigger placeholder" data-value="">请选择集群</button>
636
+ <div class="select-content">
637
+ <div class="select-item" data-value="cluster-prod-01">cluster-prod-01</div>
638
+ <div class="select-item" data-value="cluster-staging-02">cluster-staging-02</div>
639
+ </div>
640
+ </div>
641
+ </div>
642
+ </div>
643
+
644
+ <div class="form-row">
645
+ <div class="form-label">命名空间<span class="required">*</span></div>
646
+ <div class="form-control">
647
+ <div class="select-wrapper">
648
+ <button type="button" class="select-trigger placeholder" data-value="">请选择命名空间</button>
649
+ <div class="select-content">
650
+ <div class="select-item" data-value="default">default</div>
651
+ <div class="select-item" data-value="higress-system">higress-system</div>
652
+ <div class="select-item" data-value="istio-system">istio-system</div>
653
+ </div>
654
+ </div>
655
+ </div>
656
+ </div>
657
+
658
+ <div class="form-row">
659
+ <div class="form-label">Ingress 类名<span class="required">*</span></div>
660
+ <div class="form-control">
661
+ <input class="input" type="text" placeholder="请输入 IngressClass 名称">
662
+ </div>
663
+ </div>
664
+
665
+ <div class="form-row">
666
+ <div class="form-label">镜像拉取密钥</div>
667
+ <div class="form-control">
668
+ <input class="input" type="text" placeholder="请输入 imagePullSecret 名称(可选)">
669
+ </div>
670
+ </div>
671
+
672
+ <div class="form-row">
673
+ <div class="form-label">服务暴露方式</div>
674
+ <div class="form-control">
675
+ <div class="select-wrapper">
676
+ <button type="button" class="select-trigger" data-value="LoadBalancer(负载均衡)">LoadBalancer(负载均衡)</button>
677
+ <div class="select-content">
678
+ <div class="select-item selected" data-value="LoadBalancer(负载均衡)">LoadBalancer(负载均衡)</div>
679
+ <div class="select-item" data-value="NodePort(节点端口)">NodePort(节点端口)</div>
680
+ <div class="select-item" data-value="ClusterIP(集群内部)">ClusterIP(集群内部)</div>
681
+ </div>
682
+ </div>
683
+ </div>
684
+ </div>
685
+ </div>
686
+
687
+ <!-- Step 3: 可观测性配置 -->
688
+ <div class="wizard-step-content" id="step-3">
689
+ <h2 class="section-title">可观测性配置</h2>
690
+ <p class="section-desc">启用日志和监控服务</p>
691
+
692
+ <div class="form-row">
693
+ <div class="form-label">日志服务</div>
694
+ <div class="form-control">
695
+ <div style="display: flex; align-items: center; gap: 8px;">
696
+ <label class="switch">
697
+ <input type="checkbox" checked>
698
+ <span class="switch-track"></span>
699
+ <span class="switch-thumb"></span>
700
+ </label>
701
+ <span class="form-description" style="margin: 0;">开启后为实例采集运行日志</span>
702
+ </div>
703
+ </div>
704
+ </div>
705
+
706
+ <div class="form-row">
707
+ <div class="form-label">Prometheus 监控</div>
708
+ <div class="form-control">
709
+ <div style="display: flex; align-items: center; gap: 8px;">
710
+ <label class="switch">
711
+ <input type="checkbox" checked>
712
+ <span class="switch-track"></span>
713
+ <span class="switch-thumb"></span>
714
+ </label>
715
+ <span class="form-description" style="margin: 0;">启用 Prometheus 指标采集</span>
716
+ </div>
717
+ </div>
718
+ </div>
719
+ </div>
720
+
721
+ </div><!-- /.card-scrollable -->
722
+
723
+ <!-- Footer -->
724
+ <div class="settings-footer">
725
+ <button class="btn btn-link" id="btnCancel">取消</button>
726
+ <button class="btn btn-secondary" id="btnPrev" style="display: none;">上一步</button>
727
+ <button class="btn btn-primary" id="btnNext">下一步</button>
728
+ <button class="btn btn-primary" id="btnSubmit" style="display: none;">提交</button>
729
+ </div>
730
+ </div><!-- /.card -->
731
+ </div><!-- /.content-wrapper -->
732
+ </div><!-- /.page-container -->
733
+
734
+ <script>
735
+ // ===== Wizard Step Logic =====
736
+ let currentStep = 1;
737
+ const totalSteps = 3;
738
+
739
+ const stepItems = document.querySelectorAll('.step-item');
740
+ const stepContents = document.querySelectorAll('.wizard-step-content');
741
+ const btnPrev = document.getElementById('btnPrev');
742
+ const btnNext = document.getElementById('btnNext');
743
+ const btnSubmit = document.getElementById('btnSubmit');
744
+ const btnCancel = document.getElementById('btnCancel');
745
+
746
+ function updateWizard() {
747
+ // Update step indicators
748
+ stepItems.forEach((item, idx) => {
749
+ const stepNum = idx + 1;
750
+ item.classList.remove('active', 'completed');
751
+ const circle = item.querySelector('.step-circle');
752
+ const labelText = item.querySelector('.step-label').textContent.trim();
753
+ if (stepNum < currentStep) {
754
+ item.classList.add('completed');
755
+ circle.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
756
+ circle.setAttribute('aria-label', '第 ' + stepNum + ' 步:' + labelText + '(已完成)');
757
+ } else if (stepNum === currentStep) {
758
+ item.classList.add('active');
759
+ circle.textContent = stepNum;
760
+ circle.setAttribute('aria-label', '第 ' + stepNum + ' 步:' + labelText);
761
+ } else {
762
+ circle.textContent = stepNum;
763
+ circle.setAttribute('aria-label', '第 ' + stepNum + ' 步:' + labelText);
764
+ }
765
+ // ARIA tab state & roving tabindex
766
+ item.setAttribute('aria-selected', stepNum === currentStep ? 'true' : 'false');
767
+ item.setAttribute('tabindex', stepNum === currentStep ? '0' : '-1');
768
+ });
769
+
770
+ // Update step content visibility
771
+ stepContents.forEach((content, idx) => {
772
+ content.classList.toggle('active', idx + 1 === currentStep);
773
+ });
774
+
775
+ // Update buttons
776
+ btnPrev.style.display = currentStep > 1 ? 'inline-flex' : 'none';
777
+ btnNext.style.display = currentStep < totalSteps ? 'inline-flex' : 'none';
778
+ btnSubmit.style.display = currentStep === totalSteps ? 'inline-flex' : 'none';
779
+ }
780
+
781
+ // ===== Steps Keyboard Navigation =====
782
+ const stepsTablist = document.querySelector('.steps[role="tablist"]');
783
+ if (stepsTablist) {
784
+ stepsTablist.addEventListener('keydown', function(e) {
785
+ if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
786
+ e.preventDefault();
787
+ if (e.key === 'ArrowRight' && currentStep < totalSteps) {
788
+ currentStep++;
789
+ updateWizard();
790
+ } else if (e.key === 'ArrowLeft' && currentStep > 1) {
791
+ currentStep--;
792
+ updateWizard();
793
+ }
794
+ // Move focus to the now-active step item
795
+ const activeItem = stepsTablist.querySelector('.step-item.active');
796
+ if (activeItem) activeItem.focus();
797
+ });
798
+ }
799
+
800
+ btnNext.addEventListener('click', function() {
801
+ if (currentStep < totalSteps) { currentStep++; updateWizard(); }
802
+ });
803
+
804
+ btnPrev.addEventListener('click', function() {
805
+ if (currentStep > 1) { currentStep--; updateWizard(); }
806
+ });
807
+
808
+ btnSubmit.addEventListener('click', function() {
809
+ alert('表单已提交!');
810
+ });
811
+
812
+ btnCancel.addEventListener('click', function() {
813
+ if (confirm('确定要取消吗?未保存的内容将丢失。')) {
814
+ currentStep = 1; updateWizard();
815
+ }
816
+ });
817
+
818
+ // ===== Sidebar collapse toggle =====
819
+ const sidebar = document.querySelector('.sidebar');
820
+ document.querySelector('.sidebar-collapse-btn').addEventListener('click', function() {
821
+ sidebar.classList.toggle('collapsed');
822
+ });
823
+ document.getElementById('sidebarLogoArea').addEventListener('click', function() {
824
+ if (sidebar.classList.contains('collapsed')) sidebar.classList.remove('collapsed');
825
+ });
826
+
827
+ // Sidebar expand toggle
828
+ document.querySelectorAll('.sidebar-menu-item[data-toggle]').forEach(item => {
829
+ item.addEventListener('click', function(e) {
830
+ e.preventDefault();
831
+ this.classList.toggle('expanded');
832
+ const target = document.getElementById(this.getAttribute('data-toggle'));
833
+ if (target) target.classList.toggle('show');
834
+ });
835
+ });
836
+
837
+ // ===== Custom Select =====
838
+ document.querySelectorAll('.select-wrapper').forEach(wrapper => {
839
+ const trigger = wrapper.querySelector('.select-trigger');
840
+ const content = wrapper.querySelector('.select-content');
841
+ const items = wrapper.querySelectorAll('.select-item');
842
+ trigger.addEventListener('click', function(e) {
843
+ e.stopPropagation();
844
+ document.querySelectorAll('.select-content.show').forEach(c => { if (c !== content) c.classList.remove('show'); });
845
+ content.classList.toggle('show');
846
+ });
847
+ items.forEach(item => {
848
+ item.addEventListener('click', function() {
849
+ items.forEach(i => i.classList.remove('selected'));
850
+ this.classList.add('selected');
851
+ trigger.textContent = this.textContent;
852
+ trigger.dataset.value = this.dataset.value;
853
+ trigger.classList.remove('placeholder');
854
+ content.classList.remove('show');
855
+ });
856
+ });
857
+ });
858
+ document.addEventListener('click', function(e) {
859
+ if (!e.target.closest('.select-wrapper')) {
860
+ document.querySelectorAll('.select-content.show').forEach(c => c.classList.remove('show'));
861
+ }
862
+ });
863
+
864
+ // ===== Stepper =====
865
+ document.querySelectorAll('.stepper-btn').forEach(btn => {
866
+ btn.addEventListener('click', function() {
867
+ const input = this.parentElement.querySelector('input[type="number"]');
868
+ const min = parseInt(input.min) || 1;
869
+ let val = parseInt(input.value) || min;
870
+ if (this.textContent.trim() === '+') val++;
871
+ else if (val > min) val--;
872
+ input.value = val;
873
+ });
874
+ });
875
+ </script>
876
+ </body>
877
+ </html>