diff --git a/web/admin-spa/src/components/common/ActionDropdown.vue b/web/admin-spa/src/components/common/ActionDropdown.vue
new file mode 100644
index 00000000..64ba3423
--- /dev/null
+++ b/web/admin-spa/src/components/common/ActionDropdown.vue
@@ -0,0 +1,174 @@
+
+
|
-
-
-
- |
- - 名称 - - - | -- 平台/类型 - - - | -- 到期时间 - - - | -- 状态 - - - | -- 优先级 - - - | -- 代理 - | -- 今日使用 - | -
-
- 会话窗口
-
-
-
-
+
-
-
-
- Claude 系列
-
-
- 会话窗口进度表示 5 小时窗口的时间推移,颜色提示当前调度状态。
-
-
-
-
-
- 正常:请求正常处理
-
-
-
- 警告:接近限制
-
-
-
- 拒绝:达到速率限制
-
-
-
-
-
- OpenAI
-
-
- 进度条分别展示 5h 与周限窗口的额度使用比例,颜色含义与上方保持一致。
-
-
-
-
-
- 5h 窗口:5小时使用量进度,到达重置时间后会自动归零。
-
-
-
- 周限窗口:7天使用量进度,重置时同样回到 0%。
-
-
-
- 当“重置剩余”为 0 时,进度条与百分比会同步清零。
-
-
-
-
- Claude OAuth 账户
-
-
- 展示三个窗口的使用率(utilization百分比),颜色含义同上。
-
-
-
-
-
- 5h 窗口:5小时滑动窗口的使用率。
-
-
-
- 7d 窗口:7天总限额的使用率。
-
-
-
- Sonnet窗口:7天Sonnet模型专用限额。
-
-
-
- 到达重置时间后自动归零。
-
-
+
+ {{ formatLastUsed(account.lastUsedAt) }}
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
|---|
| + |
|
名称 @@ -285,17 +289,17 @@ | 所属账号 | 标签 | 状态 @@ -310,7 +314,7 @@ | 限制 | Token | 请求数 | 最后使用 @@ -361,7 +365,7 @@ | 创建时间 @@ -376,7 +380,7 @@ | 过期时间 @@ -391,7 +395,7 @@ | 操作 | @@ -410,7 +414,10 @@ 'hover:bg-blue-50/60 hover:shadow-sm dark:hover:bg-blue-900/20' ]" > -+ |
|
- + |
-
+
+
- 模型
+ 模型
+
+
+
+
+
+
+
|
|---|
| 名称 | 所属账号 | 创建者 | 创建时间 | 删除者 | 删除时间 | 费用 | Token | 请求数 | 最后使用 | 操作 | @@ -1785,7 +1822,7 @@
|---|---|---|---|---|---|---|---|---|---|---|
| + |
{
loadApiKeys()
}
+// 获取API Key的操作菜单项(用于ActionDropdown)
+const getApiKeyActions = (key) => {
+ const actions = [
+ {
+ key: 'edit',
+ label: '编辑',
+ icon: 'fa-edit',
+ color: 'blue',
+ handler: () => openEditApiKeyModal(key)
+ }
+ ]
+
+ // 如果需要续期
+ if (key.expiresAt && (isApiKeyExpired(key.expiresAt) || isApiKeyExpiringSoon(key.expiresAt))) {
+ actions.push({
+ key: 'renew',
+ label: '续期',
+ icon: 'fa-clock',
+ color: 'green',
+ handler: () => openRenewApiKeyModal(key)
+ })
+ }
+
+ // 激活/禁用
+ actions.push({
+ key: 'toggle',
+ label: key.isActive ? '禁用' : '激活',
+ icon: key.isActive ? 'fa-ban' : 'fa-check-circle',
+ color: key.isActive ? 'orange' : 'green',
+ handler: () => toggleApiKeyStatus(key)
+ })
+
+ // 删除
+ actions.push({
+ key: 'delete',
+ label: '删除',
+ icon: 'fa-trash',
+ color: 'red',
+ handler: () => deleteApiKey(key.id)
+ })
+
+ return actions
+}
+
// 切换API Key状态(激活/禁用)
const toggleApiKeyStatus = async (key) => {
let confirmed = true
@@ -4689,6 +4771,10 @@ onUnmounted(() => {
position: relative;
}
+.dark .table-wrapper {
+ border-color: rgba(255, 255, 255, 0.1);
+}
+
.table-container {
overflow-x: auto;
overflow-y: hidden;
@@ -4696,12 +4782,14 @@ onUnmounted(() => {
padding: 0;
max-width: 100%;
position: relative;
+ -webkit-overflow-scrolling: touch;
}
/* 防止表格内容溢出,保证横向滚动 */
.table-container table {
- min-width: 1200px;
+ min-width: 1400px;
border-collapse: collapse;
+ table-layout: auto;
}
.table-container::-webkit-scrollbar {
@@ -4722,6 +4810,18 @@ onUnmounted(() => {
background: #9ca3af;
}
+.dark .table-container::-webkit-scrollbar-track {
+ background: #374151;
+}
+
+.dark .table-container::-webkit-scrollbar-thumb {
+ background: #4b5563;
+}
+
+.dark .table-container::-webkit-scrollbar-thumb:hover {
+ background: #6b7280;
+}
+
.table-row {
transition: background-color 0.2s ease;
}
@@ -4738,13 +4838,43 @@ onUnmounted(() => {
.operations-column {
position: sticky;
right: 0;
- background: inherit;
- background-color: inherit;
z-index: 12;
}
+/* 确保操作列在浅色模式下有正确的背景 */
.table-container thead .operations-column {
z-index: 30;
+ background: linear-gradient(to bottom, #f9fafb, rgba(243, 244, 246, 0.9));
+}
+
+.dark .table-container thead .operations-column {
+ background: linear-gradient(to bottom, #374151, rgba(31, 41, 55, 0.9));
+}
+
+/* tbody 中的操作列背景处理 */
+.table-container tbody tr:nth-child(odd) .operations-column {
+ background-color: #ffffff;
+}
+
+.table-container tbody tr:nth-child(even) .operations-column {
+ background-color: rgba(249, 250, 251, 0.7);
+}
+
+.dark .table-container tbody tr:nth-child(odd) .operations-column {
+ background-color: rgba(31, 41, 55, 0.4);
+}
+
+.dark .table-container tbody tr:nth-child(even) .operations-column {
+ background-color: rgba(55, 65, 81, 0.3);
+}
+
+/* hover 状态下的操作列背景 */
+.table-container tbody tr:hover .operations-column {
+ background-color: rgba(239, 246, 255, 0.6);
+}
+
+.dark .table-container tbody tr:hover .operations-column {
+ background-color: rgba(30, 58, 138, 0.2);
}
.table-container tbody .operations-column {
@@ -4755,6 +4885,66 @@ onUnmounted(() => {
box-shadow: -8px 0 12px -8px rgba(30, 41, 59, 0.45);
}
+/* 固定左侧列(复选框和名称列)*/
+.checkbox-column,
+.name-column {
+ position: sticky;
+ z-index: 12;
+}
+
+/* 表头左侧固定列背景 */
+.table-container thead .checkbox-column,
+.table-container thead .name-column {
+ z-index: 30;
+ background: linear-gradient(to bottom, #f9fafb, rgba(243, 244, 246, 0.9));
+}
+
+.dark .table-container thead .checkbox-column,
+.dark .table-container thead .name-column {
+ background: linear-gradient(to bottom, #374151, rgba(31, 41, 55, 0.9));
+}
+
+/* tbody 中的左侧固定列背景处理 */
+.table-container tbody tr:nth-child(odd) .checkbox-column,
+.table-container tbody tr:nth-child(odd) .name-column {
+ background-color: #ffffff;
+}
+
+.table-container tbody tr:nth-child(even) .checkbox-column,
+.table-container tbody tr:nth-child(even) .name-column {
+ background-color: rgba(249, 250, 251, 0.7);
+}
+
+.dark .table-container tbody tr:nth-child(odd) .checkbox-column,
+.dark .table-container tbody tr:nth-child(odd) .name-column {
+ background-color: rgba(31, 41, 55, 0.4);
+}
+
+.dark .table-container tbody tr:nth-child(even) .checkbox-column,
+.dark .table-container tbody tr:nth-child(even) .name-column {
+ background-color: rgba(55, 65, 81, 0.3);
+}
+
+/* hover 状态下的左侧固定列背景 */
+.table-container tbody tr:hover .checkbox-column,
+.table-container tbody tr:hover .name-column {
+ background-color: rgba(239, 246, 255, 0.6);
+}
+
+.dark .table-container tbody tr:hover .checkbox-column,
+.dark .table-container tbody tr:hover .name-column {
+ background-color: rgba(30, 58, 138, 0.2);
+}
+
+/* 名称列右侧阴影(分隔效果) */
+.table-container tbody .name-column {
+ box-shadow: 8px 0 12px -8px rgba(15, 23, 42, 0.16);
+}
+
+.dark .table-container tbody .name-column {
+ box-shadow: 8px 0 12px -8px rgba(30, 41, 59, 0.45);
+}
+
.loading-spinner {
width: 24px;
height: 24px;
|