工具助手添加
This commit is contained in:
@@ -0,0 +1,894 @@
|
||||
/**
|
||||
* API Tester - 科技感未来主题样式
|
||||
* @author huazm
|
||||
*/
|
||||
|
||||
/* ==================== CSS 变量和主题 ==================== */
|
||||
:root {
|
||||
/* 默认主题: 深蓝科技 */
|
||||
--bg-primary: #0a0e17;
|
||||
--bg-secondary: #111827;
|
||||
--bg-tertiary: #1f2937;
|
||||
--bg-hover: #374151;
|
||||
|
||||
--text-primary: #f3f4f6;
|
||||
--text-secondary: #9ca3af;
|
||||
--text-muted: #6b7280;
|
||||
|
||||
--accent-primary: #3b82f6;
|
||||
--accent-secondary: #60a5fa;
|
||||
--accent-glow: rgba(59, 130, 246, 0.5);
|
||||
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--error: #ef4444;
|
||||
--info: #06b6d4;
|
||||
|
||||
--border-color: #374151;
|
||||
--border-glow: rgba(59, 130, 246, 0.3);
|
||||
|
||||
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
|
||||
--shadow-glow: 0 0 20px var(--accent-glow);
|
||||
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* 主题: 紫色星云 */
|
||||
[data-theme="nebula"] {
|
||||
--bg-primary: #0f0a1a;
|
||||
--bg-secondary: #1a1028;
|
||||
--bg-tertiary: #2d1f4a;
|
||||
--accent-primary: #a855f7;
|
||||
--accent-secondary: #c084fc;
|
||||
--accent-glow: rgba(168, 85, 247, 0.5);
|
||||
--border-glow: rgba(168, 85, 247, 0.3);
|
||||
}
|
||||
|
||||
/* 主题: 翠绿矩阵 */
|
||||
[data-theme="matrix"] {
|
||||
--bg-primary: #0a0f0a;
|
||||
--bg-secondary: #0f1a0f;
|
||||
--bg-tertiary: #1a2f1a;
|
||||
--accent-primary: #22c55e;
|
||||
--accent-secondary: #4ade80;
|
||||
--accent-glow: rgba(34, 197, 94, 0.5);
|
||||
--border-glow: rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
/* 主题: 赛博橙 */
|
||||
[data-theme="cyber"] {
|
||||
--bg-primary: #0f0a05;
|
||||
--bg-secondary: #1a1008;
|
||||
--bg-tertiary: #2f1f0a;
|
||||
--accent-primary: #f97316;
|
||||
--accent-secondary: #fb923c;
|
||||
--accent-glow: rgba(249, 115, 22, 0.5);
|
||||
--border-glow: rgba(249, 115, 22, 0.3);
|
||||
}
|
||||
|
||||
/* 主题: 冰霜白 */
|
||||
[data-theme="frost"] {
|
||||
--bg-primary: #f0f4f8;
|
||||
--bg-secondary: #e2e8f0;
|
||||
--bg-tertiary: #cbd5e1;
|
||||
--bg-hover: #94a3b8;
|
||||
--text-primary: #1e293b;
|
||||
--text-secondary: #475569;
|
||||
--text-muted: #64748b;
|
||||
--accent-primary: #0ea5e9;
|
||||
--accent-secondary: #38bdf8;
|
||||
--border-color: #94a3b8;
|
||||
}
|
||||
|
||||
/* ==================== 基础样式 ==================== */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ==================== 粒子背景 ==================== */
|
||||
#particles-canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ==================== 应用容器 ==================== */
|
||||
.app-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ==================== 头部导航 ==================== */
|
||||
.app-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 24px;
|
||||
background: linear-gradient(180deg, var(--bg-secondary) 0%, transparent 100%);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 24px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--accent-primary),
|
||||
var(--accent-secondary)
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.logo-badge {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
padding: 8px 16px;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-md);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.nav-btn:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.nav-btn.active {
|
||||
color: var(--accent-primary);
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.theme-btn,
|
||||
.voice-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-tertiary);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.theme-btn:hover,
|
||||
.voice-btn:hover {
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: var(--shadow-glow);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.voice-btn.recording {
|
||||
animation: recording 1s infinite;
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
@keyframes recording {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 10px rgba(239, 68, 68, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 主内容区 ==================== */
|
||||
.app-main {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
max-width: 1800px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.app-main {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 面板通用样式 ==================== */
|
||||
.panel {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ==================== URL 栏 ==================== */
|
||||
.url-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--bg-tertiary) 0%,
|
||||
var(--bg-secondary) 100%
|
||||
);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.method-select {
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--accent-primary);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
min-width: 100px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.method-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
.url-input {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 14px;
|
||||
border-radius: var(--radius-md);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.url-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
.url-input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ==================== 发送按钮 ==================== */
|
||||
.send-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--accent-primary),
|
||||
var(--accent-secondary)
|
||||
);
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.send-btn::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.2),
|
||||
transparent
|
||||
);
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.send-btn:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.send-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px var(--accent-glow);
|
||||
}
|
||||
|
||||
.send-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.send-btn.loading {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.send-btn.loading .btn-text {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.send-btn.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid white;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 标签页 ==================== */
|
||||
.request-tabs,
|
||||
.response-tabs {
|
||||
display: flex;
|
||||
padding: 0 20px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 12px 16px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: var(--accent-primary);
|
||||
border-bottom-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
/* ==================== 内容区 ==================== */
|
||||
.request-content,
|
||||
.response-content {
|
||||
padding: 20px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ==================== Key-Value 编辑器 ==================== */
|
||||
.kv-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.kv-key,
|
||||
.kv-value {
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.kv-key:focus,
|
||||
.kv-value:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.kv-remove {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-muted);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.kv-remove:hover {
|
||||
background: var(--error);
|
||||
border-color: var(--error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.add-row-btn {
|
||||
margin-top: 8px;
|
||||
padding: 8px 16px;
|
||||
background: transparent;
|
||||
border: 1px dashed var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.add-row-btn:hover {
|
||||
border-color: var(--accent-primary);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
/* ==================== Header 预设 ==================== */
|
||||
.header-presets-container {
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.preset-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.preset-group:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.preset-label {
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.preset-btn {
|
||||
padding: 5px 12px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.preset-btn:hover {
|
||||
background: var(--accent-primary);
|
||||
border-color: var(--accent-primary);
|
||||
color: white;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px var(--accent-glow);
|
||||
}
|
||||
|
||||
.preset-btn.auth-preset {
|
||||
border-color: var(--warning);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.preset-btn.auth-preset:hover {
|
||||
background: var(--warning);
|
||||
border-color: var(--warning);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ==================== Body 编辑器 ==================== */
|
||||
.body-type-selector {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.body-type-selector label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.body-type-selector input[type="radio"] {
|
||||
accent-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.body-editor-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.body-editor {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
padding: 16px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
border-radius: var(--radius-md);
|
||||
resize: vertical;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.body-editor:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.format-btn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
padding: 6px 12px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.format-btn:hover {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ==================== Auth 配置 ==================== */
|
||||
.auth-type-selector {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.auth-type-selector select {
|
||||
padding: 10px 16px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.auth-config {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.auth-input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.auth-input-group label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.auth-input-group input {
|
||||
padding: 10px 12px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* ==================== 响应面板 ==================== */
|
||||
.response-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.response-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
padding: 4px 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.status-code.success {
|
||||
color: var(--success);
|
||||
}
|
||||
.status-code.redirect {
|
||||
color: var(--warning);
|
||||
}
|
||||
.status-code.error {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.status-text {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.response-meta {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.response-body,
|
||||
.response-headers-view {
|
||||
background: var(--bg-primary);
|
||||
padding: 16px;
|
||||
border-radius: var(--radius-md);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* ==================== 历史记录面板 ==================== */
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.panel-header h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.panel-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
border-radius: var(--radius-sm);
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
padding: 8px 16px;
|
||||
background: var(--error);
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.clear-btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.history-item:hover {
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.history-method {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.history-method.get {
|
||||
background: #10b98120;
|
||||
color: var(--success);
|
||||
}
|
||||
.history-method.post {
|
||||
background: #3b82f620;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
.history-method.put {
|
||||
background: #f59e0b20;
|
||||
color: var(--warning);
|
||||
}
|
||||
.history-method.delete {
|
||||
background: #ef444420;
|
||||
color: var(--error);
|
||||
}
|
||||
.history-method.patch {
|
||||
background: #a855f720;
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
.history-url {
|
||||
flex: 1;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.history-time {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.history-status {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* ==================== 滚动条样式 ==================== */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-hover);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
/**
|
||||
* API Tester - 前端应用主逻辑
|
||||
* @author huazm
|
||||
*/
|
||||
|
||||
// ==================== 粒子背景 ====================
|
||||
class ParticleBackground {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext('2d');
|
||||
this.particles = [];
|
||||
this.particleCount = 80;
|
||||
this.resize();
|
||||
this.init();
|
||||
this.animate();
|
||||
window.addEventListener('resize', () => this.resize());
|
||||
}
|
||||
|
||||
resize() {
|
||||
this.canvas.width = window.innerWidth;
|
||||
this.canvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.particles = [];
|
||||
for (let i = 0; i < this.particleCount; i++) {
|
||||
this.particles.push({
|
||||
x: Math.random() * this.canvas.width,
|
||||
y: Math.random() * this.canvas.height,
|
||||
vx: (Math.random() - 0.5) * 0.5,
|
||||
vy: (Math.random() - 0.5) * 0.5,
|
||||
size: Math.random() * 2 + 1,
|
||||
opacity: Math.random() * 0.5 + 0.2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
animate() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// 获取当前主题色
|
||||
const style = getComputedStyle(document.documentElement);
|
||||
const accentColor = style.getPropertyValue('--accent-primary').trim() || '#3b82f6';
|
||||
|
||||
this.particles.forEach((p, i) => {
|
||||
// 更新位置
|
||||
p.x += p.vx;
|
||||
p.y += p.vy;
|
||||
|
||||
// 边界检测
|
||||
if (p.x < 0 || p.x > this.canvas.width) p.vx *= -1;
|
||||
if (p.y < 0 || p.y > this.canvas.height) p.vy *= -1;
|
||||
|
||||
// 绘制粒子
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = accentColor.replace(')', `, ${p.opacity})`).replace('rgb', 'rgba');
|
||||
this.ctx.fill();
|
||||
|
||||
// 连接附近粒子
|
||||
for (let j = i + 1; j < this.particles.length; j++) {
|
||||
const p2 = this.particles[j];
|
||||
const dx = p.x - p2.x;
|
||||
const dy = p.y - p2.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist < 150) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(p.x, p.y);
|
||||
this.ctx.lineTo(p2.x, p2.y);
|
||||
this.ctx.strokeStyle = accentColor.replace(')', `, ${0.1 * (1 - dist / 150)})`).replace('rgb', 'rgba');
|
||||
this.ctx.stroke();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
requestAnimationFrame(() => this.animate());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 主题管理 ====================
|
||||
const themes = ['default', 'nebula', 'matrix', 'cyber', 'frost'];
|
||||
let currentThemeIndex = 0;
|
||||
|
||||
function toggleTheme() {
|
||||
currentThemeIndex = (currentThemeIndex + 1) % themes.length;
|
||||
const theme = themes[currentThemeIndex];
|
||||
|
||||
if (theme === 'default') {
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
localStorage.setItem('api-tester-theme', theme);
|
||||
}
|
||||
|
||||
function loadTheme() {
|
||||
const saved = localStorage.getItem('api-tester-theme');
|
||||
if (saved && themes.includes(saved)) {
|
||||
currentThemeIndex = themes.indexOf(saved);
|
||||
if (saved !== 'default') {
|
||||
document.documentElement.setAttribute('data-theme', saved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== API 请求 ====================
|
||||
async function sendRequest() {
|
||||
const sendBtn = document.getElementById('send-btn');
|
||||
const method = document.getElementById('method-select').value;
|
||||
const url = document.getElementById('url-input').value.trim();
|
||||
|
||||
if (!url) {
|
||||
showToast('请输入 URL', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集请求数据
|
||||
const headers = collectKeyValues('headers-editor');
|
||||
const params = collectKeyValues('params-editor');
|
||||
const bodyType = document.querySelector('input[name="bodyType"]:checked')?.value || 'none';
|
||||
const body = bodyType !== 'none' ? document.getElementById('body-editor').value : null;
|
||||
const authType = document.getElementById('auth-type').value;
|
||||
const authConfig = collectAuthConfig(authType);
|
||||
|
||||
// 显示加载状态
|
||||
sendBtn.classList.add('loading');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/request', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
method, url, headers, params, body,
|
||||
bodyType: bodyType !== 'none' ? bodyType : null,
|
||||
authType: authType !== 'none' ? authType : null,
|
||||
authConfig
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
displayResponse(result);
|
||||
|
||||
} catch (error) {
|
||||
displayResponse({ success: false, error: error.message });
|
||||
} finally {
|
||||
sendBtn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
function collectKeyValues(editorId) {
|
||||
const editor = document.getElementById(editorId);
|
||||
const rows = editor.querySelectorAll('.kv-row');
|
||||
const result = {};
|
||||
|
||||
rows.forEach(row => {
|
||||
const key = row.querySelector('.kv-key')?.value.trim();
|
||||
const value = row.querySelector('.kv-value')?.value.trim();
|
||||
if (key) result[key] = value || '';
|
||||
});
|
||||
|
||||
return Object.keys(result).length > 0 ? result : null;
|
||||
}
|
||||
|
||||
function collectAuthConfig(authType) {
|
||||
if (authType === 'none') return null;
|
||||
|
||||
const config = {};
|
||||
const container = document.getElementById('auth-config');
|
||||
const inputs = container.querySelectorAll('input');
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (input.dataset.key) {
|
||||
config[input.dataset.key] = input.value;
|
||||
}
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function displayResponse(result) {
|
||||
const statusCode = document.getElementById('status-code');
|
||||
const statusText = document.getElementById('status-text');
|
||||
const responseTime = document.getElementById('response-time');
|
||||
const responseSize = document.getElementById('response-size');
|
||||
const responseBody = document.getElementById('response-body');
|
||||
const responseHeaders = document.getElementById('response-headers-view');
|
||||
|
||||
if (result.success) {
|
||||
const code = result.statusCode;
|
||||
statusCode.textContent = code;
|
||||
statusCode.className = 'status-code ' + (code < 300 ? 'success' : code < 400 ? 'redirect' : 'error');
|
||||
statusText.textContent = getStatusText(code);
|
||||
responseTime.textContent = `${result.duration}ms`;
|
||||
responseSize.textContent = formatBytes(result.size || 0);
|
||||
|
||||
// 格式化响应体
|
||||
let body = result.body;
|
||||
try {
|
||||
const parsed = JSON.parse(body);
|
||||
body = JSON.stringify(parsed, null, 2);
|
||||
} catch {}
|
||||
responseBody.querySelector('code').textContent = body;
|
||||
|
||||
// 响应头
|
||||
if (result.headers) {
|
||||
responseHeaders.querySelector('code').textContent =
|
||||
JSON.stringify(result.headers, null, 2);
|
||||
}
|
||||
} else {
|
||||
statusCode.textContent = 'ERR';
|
||||
statusCode.className = 'status-code error';
|
||||
statusText.textContent = result.error || '请求失败';
|
||||
responseTime.textContent = result.duration ? `${result.duration}ms` : '--';
|
||||
responseSize.textContent = '--';
|
||||
responseBody.querySelector('code').textContent = result.error || '请求失败';
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusText(code) {
|
||||
const statusTexts = {
|
||||
200: 'OK', 201: 'Created', 204: 'No Content',
|
||||
301: 'Moved Permanently', 302: 'Found', 304: 'Not Modified',
|
||||
400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden',
|
||||
404: 'Not Found', 405: 'Method Not Allowed', 500: 'Internal Server Error',
|
||||
502: 'Bad Gateway', 503: 'Service Unavailable'
|
||||
};
|
||||
return statusTexts[code] || 'Unknown';
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.textContent = message;
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 24px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--${type === 'error' ? 'error' : 'accent-primary'});
|
||||
color: var(--text-primary);
|
||||
border-radius: 8px;
|
||||
z-index: 9999;
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(() => toast.remove(), 3000);
|
||||
}
|
||||
|
||||
// ==================== 认证配置 UI ====================
|
||||
function updateAuthUI(authType) {
|
||||
const container = document.getElementById('auth-config');
|
||||
container.innerHTML = '';
|
||||
|
||||
const configs = {
|
||||
bearer: [{ key: 'token', label: 'Token', type: 'password' }],
|
||||
basic: [
|
||||
{ key: 'username', label: '用户名', type: 'text' },
|
||||
{ key: 'password', label: '密码', type: 'password' }
|
||||
],
|
||||
apikey: [
|
||||
{ key: 'key', label: 'Key Name', type: 'text' },
|
||||
{ key: 'value', label: 'Key Value', type: 'password' },
|
||||
{ key: 'location', label: '位置 (header/query)', type: 'text', default: 'header' }
|
||||
]
|
||||
};
|
||||
|
||||
const fields = configs[authType] || [];
|
||||
|
||||
fields.forEach(field => {
|
||||
const group = document.createElement('div');
|
||||
group.className = 'auth-input-group';
|
||||
group.innerHTML = `
|
||||
<label>${field.label}</label>
|
||||
<input type="${field.type}" data-key="${field.key}"
|
||||
value="${field.default || ''}" placeholder="${field.label}">
|
||||
`;
|
||||
container.appendChild(group);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== Key-Value 编辑器 ====================
|
||||
function addKVRow(editorId) {
|
||||
const editor = document.getElementById(editorId);
|
||||
const row = document.createElement('div');
|
||||
row.className = 'kv-row';
|
||||
row.innerHTML = `
|
||||
<input type="text" class="kv-key" placeholder="Key">
|
||||
<input type="text" class="kv-value" placeholder="Value">
|
||||
<button class="kv-remove">×</button>
|
||||
`;
|
||||
editor.appendChild(row);
|
||||
|
||||
row.querySelector('.kv-remove').addEventListener('click', () => row.remove());
|
||||
}
|
||||
|
||||
// ==================== 标签页切换 ====================
|
||||
function setupTabs() {
|
||||
// 请求配置标签页
|
||||
document.querySelectorAll('.request-tabs .tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.request-tabs .tab-btn').forEach(b => b.classList.remove('active'));
|
||||
document.querySelectorAll('.request-content .tab-content').forEach(c => c.classList.remove('active'));
|
||||
|
||||
btn.classList.add('active');
|
||||
document.getElementById(`${btn.dataset.tab}-content`).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// 响应标签页
|
||||
document.querySelectorAll('.response-tabs .tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.response-tabs .tab-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
const isBody = btn.dataset.tab === 'response-body';
|
||||
document.getElementById('response-body').classList.toggle('hidden', !isBody);
|
||||
document.getElementById('response-headers-view').classList.toggle('hidden', isBody);
|
||||
});
|
||||
});
|
||||
|
||||
// 主导航标签页
|
||||
document.querySelectorAll('.header-nav .nav-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.header-nav .nav-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
// 切换面板显示
|
||||
const tab = btn.dataset.tab;
|
||||
document.getElementById('request-panel').classList.toggle('hidden', tab !== 'request');
|
||||
document.getElementById('response-panel').classList.toggle('hidden', tab !== 'request');
|
||||
document.getElementById('history-panel').classList.toggle('hidden', tab !== 'history');
|
||||
|
||||
if (tab === 'history') loadHistory();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ==================== 历史记录 ====================
|
||||
async function loadHistory() {
|
||||
const list = document.getElementById('history-list');
|
||||
list.innerHTML = '<div style="text-align:center;color:var(--text-muted);">加载中...</div>';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/history?size=50');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.data.length > 0) {
|
||||
list.innerHTML = result.data.map(item => `
|
||||
<div class="history-item" data-id="${item.id}" onclick="loadHistoryItem(${item.id})">
|
||||
<span class="history-method ${item.method.toLowerCase()}">${item.method}</span>
|
||||
<span class="history-url">${item.url}</span>
|
||||
<span class="history-status">${item.statusCode || '--'}</span>
|
||||
<span class="history-time">${new Date(item.createdAt).toLocaleString()}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
list.innerHTML = '<div style="text-align:center;color:var(--text-muted);">暂无历史记录</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
list.innerHTML = '<div style="text-align:center;color:var(--error);">加载失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHistoryItem(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/history?size=1000`);
|
||||
const result = await response.json();
|
||||
const item = result.data.find(h => h.id === id);
|
||||
|
||||
if (item) {
|
||||
document.getElementById('method-select').value = item.method;
|
||||
document.getElementById('url-input').value = item.url;
|
||||
|
||||
// 切换回请求面板
|
||||
document.querySelectorAll('.header-nav .nav-btn').forEach(b => b.classList.remove('active'));
|
||||
document.querySelector('.header-nav .nav-btn[data-tab="request"]').classList.add('active');
|
||||
document.getElementById('request-panel').classList.remove('hidden');
|
||||
document.getElementById('response-panel').classList.remove('hidden');
|
||||
document.getElementById('history-panel').classList.add('hidden');
|
||||
|
||||
showToast('已加载历史记录', 'info');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('加载失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 语音输入 ====================
|
||||
function setupVoiceInput() {
|
||||
const voiceBtn = document.getElementById('voice-input');
|
||||
|
||||
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
|
||||
voiceBtn.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
const recognition = new SpeechRecognition();
|
||||
recognition.lang = 'zh-CN';
|
||||
recognition.continuous = false;
|
||||
|
||||
let isRecording = false;
|
||||
|
||||
voiceBtn.addEventListener('click', () => {
|
||||
if (isRecording) {
|
||||
recognition.stop();
|
||||
voiceBtn.classList.remove('recording');
|
||||
isRecording = false;
|
||||
} else {
|
||||
recognition.start();
|
||||
voiceBtn.classList.add('recording');
|
||||
isRecording = true;
|
||||
}
|
||||
});
|
||||
|
||||
recognition.onresult = (event) => {
|
||||
const text = event.results[0][0].transcript;
|
||||
const urlInput = document.getElementById('url-input');
|
||||
urlInput.value = text;
|
||||
voiceBtn.classList.remove('recording');
|
||||
isRecording = false;
|
||||
showToast('语音识别成功', 'info');
|
||||
};
|
||||
|
||||
recognition.onerror = () => {
|
||||
voiceBtn.classList.remove('recording');
|
||||
isRecording = false;
|
||||
showToast('语音识别失败', 'error');
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== JSON 格式化 ====================
|
||||
function setupFormatButton() {
|
||||
document.getElementById('format-json-btn').addEventListener('click', () => {
|
||||
const editor = document.getElementById('body-editor');
|
||||
try {
|
||||
const parsed = JSON.parse(editor.value);
|
||||
editor.value = JSON.stringify(parsed, null, 2);
|
||||
showToast('格式化成功', 'info');
|
||||
} catch {
|
||||
showToast('JSON 格式错误', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 预设按钮 ====================
|
||||
function setupPresets() {
|
||||
document.querySelectorAll('.preset-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const key = btn.dataset.key;
|
||||
const value = btn.dataset.value;
|
||||
|
||||
// 添加到 headers 编辑器
|
||||
const editor = document.getElementById('headers-editor');
|
||||
const rows = editor.querySelectorAll('.kv-row');
|
||||
|
||||
// 检查是否已存在该 key
|
||||
let existingRow = null;
|
||||
for (const row of rows) {
|
||||
const keyInput = row.querySelector('.kv-key');
|
||||
if (keyInput.value === key) {
|
||||
existingRow = row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingRow) {
|
||||
// 更新现有行
|
||||
const valueInput = existingRow.querySelector('.kv-value');
|
||||
valueInput.value = value;
|
||||
valueInput.focus();
|
||||
// 如果是鉴权类型,光标定位到值末尾方便输入 token
|
||||
if (btn.classList.contains('auth-preset')) {
|
||||
valueInput.setSelectionRange(value.length, value.length);
|
||||
}
|
||||
} else {
|
||||
// 添加新行
|
||||
addKVRow('headers-editor');
|
||||
const newRow = editor.lastElementChild;
|
||||
newRow.querySelector('.kv-key').value = key;
|
||||
const valueInput = newRow.querySelector('.kv-value');
|
||||
valueInput.value = value;
|
||||
|
||||
// 如果是鉴权或空值类型,自动聚焦到值输入框
|
||||
if (btn.classList.contains('auth-preset') || value === '') {
|
||||
valueInput.focus();
|
||||
valueInput.setSelectionRange(value.length, value.length);
|
||||
}
|
||||
}
|
||||
|
||||
showToast(`已添加 ${key}`, 'info');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 初始化 ====================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 初始化粒子背景
|
||||
new ParticleBackground(document.getElementById('particles-canvas'));
|
||||
|
||||
// 加载主题
|
||||
loadTheme();
|
||||
|
||||
// 设置事件监听
|
||||
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
|
||||
document.getElementById('send-btn').addEventListener('click', sendRequest);
|
||||
document.getElementById('auth-type').addEventListener('change', (e) => updateAuthUI(e.target.value));
|
||||
|
||||
// 添加行按钮
|
||||
document.querySelectorAll('.add-row-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => addKVRow(btn.dataset.target));
|
||||
});
|
||||
|
||||
// 删除行按钮
|
||||
document.querySelectorAll('.kv-remove').forEach(btn => {
|
||||
btn.addEventListener('click', () => btn.parentElement.remove());
|
||||
});
|
||||
|
||||
// 清空历史
|
||||
document.getElementById('clear-history')?.addEventListener('click', async () => {
|
||||
if (confirm('确定要清空所有历史记录吗?')) {
|
||||
await fetch('/api/history/clear', { method: 'DELETE' });
|
||||
loadHistory();
|
||||
}
|
||||
});
|
||||
|
||||
// 设置标签页
|
||||
setupTabs();
|
||||
setupVoiceInput();
|
||||
setupFormatButton();
|
||||
setupPresets();
|
||||
|
||||
// 键盘快捷键
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
sendRequest();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user