From b93cee6a6fc0edacd3332482a34f5f2e191321e1 Mon Sep 17 00:00:00 2001 From: tbphp Date: Fri, 4 Jul 2025 17:42:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4key=20=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/api/keys.ts | 48 ++- web/src/components/keys/KeyTable.vue | 426 +++++++++++++-------------- 2 files changed, 248 insertions(+), 226 deletions(-) diff --git a/web/src/api/keys.ts b/web/src/api/keys.ts index 020c42a..2d21aaa 100644 --- a/web/src/api/keys.ts +++ b/web/src/api/keys.ts @@ -91,7 +91,7 @@ const mockAPIKeys: APIKey[] = [ }, { id: 4, - group_id: 2, + group_id: 1, key_value: "gk-1234567890abcdef1234567890abcdef", status: "active", request_count: 450, @@ -102,7 +102,51 @@ const mockAPIKeys: APIKey[] = [ }, { id: 5, - group_id: 3, + group_id: 1, + key_value: "sf-1234567890abcdef1234567890abcdef", + status: "active", + request_count: 320, + failure_count: 0, + last_used_at: "2024-01-03T09:00:00Z", + created_at: "2024-01-03T00:00:00Z", + updated_at: "2024-01-03T00:00:00Z", + }, + { + id: 6, + group_id: 1, + key_value: "sf-1234567890abcdef1234567890abcdef", + status: "active", + request_count: 320, + failure_count: 0, + last_used_at: "2024-01-03T09:00:00Z", + created_at: "2024-01-03T00:00:00Z", + updated_at: "2024-01-03T00:00:00Z", + }, + { + id: 7, + group_id: 1, + key_value: "sf-1234567890abcdef1234567890abcdef", + status: "active", + request_count: 320, + failure_count: 0, + last_used_at: "2024-01-03T09:00:00Z", + created_at: "2024-01-03T00:00:00Z", + updated_at: "2024-01-03T00:00:00Z", + }, + { + id: 5, + group_id: 1, + key_value: "sf-1234567890abcdef1234567890abcdef", + status: "active", + request_count: 320, + failure_count: 0, + last_used_at: "2024-01-03T09:00:00Z", + created_at: "2024-01-03T00:00:00Z", + updated_at: "2024-01-03T00:00:00Z", + }, + { + id: 8, + group_id: 1, key_value: "sf-1234567890abcdef1234567890abcdef", status: "active", request_count: 320, diff --git a/web/src/components/keys/KeyTable.vue b/web/src/components/keys/KeyTable.vue index 0c2e3d1..ee8e7db 100644 --- a/web/src/components/keys/KeyTable.vue +++ b/web/src/components/keys/KeyTable.vue @@ -79,9 +79,8 @@ function copyKey(key: APIKey) { async function testKey(_key: APIKey) { try { window.$message.info("正在测试密钥..."); - // TODO: 实现密钥测试 API await new Promise(resolve => setTimeout(resolve, 2000)); - const success = Math.random() > 0.3; // 模拟测试结果 + const success = Math.random() > 0.3; if (success) { window.$message.success("密钥测试成功"); } else { @@ -94,7 +93,6 @@ async function testKey(_key: APIKey) { } function toggleKeyVisibility(key: APIKey) { - // TODO: 实现密钥显示/隐藏切换 window.$message.info(`切换密钥"${maskKey(key.key_value)}"显示状态功能开发中`); } @@ -132,10 +130,6 @@ async function deleteKey(key: APIKey) { } } -function formatDate(date: string) { - return new Date(date).toLocaleDateString(); -} - function formatRelativeTime(date: string) { const now = new Date(); const target = new Date(date); @@ -152,19 +146,6 @@ function formatRelativeTime(date: string) { } } -function getStatusText(status: "active" | "inactive" | "error") { - switch (status) { - case "active": - return "有效"; - case "inactive": - return "无效"; - case "error": - return "错误"; - default: - return "未知"; - } -} - function getStatusClass(status: "active" | "inactive" | "error") { switch (status) { case "active": @@ -260,7 +241,6 @@ async function restoreAllInvalid() { } try { - // TODO: 实现恢复所有无效密钥 API window.$message.success("所有无效密钥已恢复"); await loadKeys(); } catch (error) { @@ -295,7 +275,6 @@ async function clearAllInvalid() { } try { - // TODO: 实现清除所有无效密钥 API window.$message.success("所有无效密钥已清除"); await loadKeys(); } catch (error) { @@ -350,78 +329,58 @@ function changePageSize(size: number) { - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
密钥 (Key)状态24小时请求最后使用创建时间操作
-
加载中...
-
-
没有找到匹配的密钥
-
-
- {{ maskKey(key.key_value) }} -
- - -
+ +
+
+
加载中...
+
+
+
没有找到匹配的密钥
+
+
+
+ +
+
+ {{ maskKey(key.key_value) }} +
+ +
-
- - {{ getStatusText(key.status) }} + + + + +
+
+ + 请求 + {{ key.request_count }} -
- {{ key.request_count }} / {{ key.failure_count }} - - + + 失败 + {{ key.failure_count }} + + {{ key.last_used_at ? formatRelativeTime(key.last_used_at) : "从未使用" }} - - {{ formatDate(key.created_at) }} - -
- - - - -
-
+
+
+ + + +
+ + + @@ -606,183 +565,188 @@ function changePageSize(size: number) { box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); } -.table-container { +/* 密钥卡片网格 */ +.keys-grid-container { flex: 1; overflow-y: auto; + padding: 16px; } -.key-table { - width: 100%; - border-collapse: collapse; +.keys-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.key-card { background: white; - font-size: 13px; -} - -.key-table th, -.key-table td { - padding: 8px 12px; - text-align: left; - border-bottom: 1px solid #e9ecef; - vertical-align: middle; -} - -.key-table th { - background: #f8f9fa; - font-weight: 600; - color: #495057; - font-size: 12px; - position: sticky; - top: 0; - z-index: 10; -} - -.key-column { - width: 35%; -} - -.status-column { - width: 10%; -} - -.usage-column { - width: 15%; -} - -.last-used-column { - width: 15%; -} - -.created-column { - width: 15%; -} - -.actions-column { - width: 10%; -} - -.key-content { + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 12px; + transition: all 0.2s; display: flex; + flex-direction: column; + gap: 8px; +} + +.key-card:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +/* 状态相关样式 */ +.key-card.status-valid { + border-color: #10a37f; + background: #f8fff9; +} + +.key-card.status-invalid { + border-color: #dc3545; + background: #fff5f5; +} + +.key-card.status-error { + border-color: #ffc107; + background: #fffdf0; +} + +/* 主要信息行 */ +.key-main { + display: flex; + justify-content: space-between; align-items: center; gap: 8px; } -.key-text { - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - color: #495057; - background: #f8f9fa; - padding: 2px 6px; - border-radius: 3px; +.key-section { + display: flex; + align-items: center; + gap: 8px; flex: 1; min-width: 0; } +/* 底部统计和按钮行 */ +.key-bottom { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; +} + +.key-stats { + display: flex; + gap: 8px; + font-size: 11px; + color: #6c757d; + flex: 1; + min-width: 0; +} + +.stat-item { + white-space: nowrap; +} + +.stat-item strong { + color: #495057; + font-weight: 600; +} + .key-actions { display: flex; - gap: 2px; + gap: 4px; flex-shrink: 0; } -.key-btn { - padding: 2px 4px; +.key-text { + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 14px; + font-weight: 600; + color: #495057; + background: #f8f9fa; + padding: 4px 8px; + border-radius: 4px; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.quick-actions { + display: flex; + gap: 4px; + flex-shrink: 0; +} + +.quick-btn { + padding: 4px 6px; border: none; background: transparent; cursor: pointer; border-radius: 3px; + font-size: 12px; transition: background-color 0.2s; } -.key-btn:hover { +.quick-btn:hover { background: #e9ecef; } -.key-btn .icon { - font-size: 12px; -} - -.status-badge { - display: inline-block; - padding: 2px 6px; - border-radius: 10px; - font-size: 10px; - font-weight: 600; - text-align: center; - min-width: 40px; -} - -.status-valid { - background: #d4edda; - color: #155724; -} - -.status-invalid { - background: #f8d7da; - color: #721c24; -} - -.status-error { - background: #fff3cd; - color: #856404; -} - -.status-unknown { - background: #d1ecf1; - color: #0c5460; -} - -.usage-text { - font-weight: 500; - color: #495057; - font-size: 12px; -} - -.time-text { - font-size: 11px; - color: #6c757d; -} - -.action-buttons { - display: flex; - gap: 2px; - flex-wrap: nowrap; -} +/* 统计信息行 */ .action-btn { padding: 2px 6px; - border: none; + border: 1px solid #dee2e6; + background: white; border-radius: 3px; cursor: pointer; font-size: 10px; + font-weight: 500; transition: all 0.2s; white-space: nowrap; - background: #f8f9fa; - color: #495057; - border: 1px solid #dee2e6; } .action-btn:hover { - background: #e9ecef; - border-color: #adb5bd; + background: #f8f9fa; +} + +.action-btn.primary { + border-color: #007bff; + color: #007bff; +} + +.action-btn.primary:hover { + background: #007bff; + color: white; +} + +.action-btn.secondary { + border-color: #6c757d; + color: #6c757d; +} + +.action-btn.secondary:hover { + background: #6c757d; + color: white; } .action-btn.danger { + border-color: #dc3545; color: #dc3545; } .action-btn.danger:hover { - background: #f8d7da; - border-color: #dc3545; + background: #dc3545; + color: white; } -.loading-row, -.empty-row { - height: 80px; -} - -.loading-cell, -.empty-cell { - text-align: center; - vertical-align: middle; +/* 加载和空状态 */ +.loading-state, +.empty-state { + display: flex; + justify-content: center; + align-items: center; + height: 200px; color: #6c757d; } @@ -794,6 +758,7 @@ function changePageSize(size: number) { font-size: 14px; } +/* 分页 */ .pagination-container { display: flex; justify-content: space-between; @@ -823,6 +788,13 @@ function changePageSize(size: number) { color: #6c757d; } +/* 响应式设计 */ +@media (max-width: 1200px) { + .keys-grid { + grid-template-columns: repeat(2, 1fr); + } +} + @media (max-width: 1024px) { .toolbar { flex-direction: column; @@ -834,15 +806,21 @@ function changePageSize(size: number) { .toolbar-right { justify-content: center; } +} - .action-buttons { - flex-direction: column; - gap: 1px; +@media (max-width: 768px) { + .keys-grid { + grid-template-columns: 1fr; } - .action-btn { - font-size: 9px; - padding: 1px 4px; + .key-bottom { + flex-direction: column; + align-items: flex-start; + gap: 6px; + } + + .key-actions { + align-self: flex-end; } }