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;
}
}