mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix: 账户时间线入口与路由修复
- 移除账户列表下拉/卡片的时间线入口,仅保留详情弹窗顶部按钮 - ActionDropdown 全局互斥,避免多菜单堆叠 - 账户筛选去重,避免“未知渠道”重复泄露
This commit is contained in:
@@ -2133,9 +2133,14 @@ router.get('/api-keys/:keyId/usage-records', authenticateAdmin, async (req, res)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accountOptions = []
|
const accountOptions = []
|
||||||
|
const accountIdAdded = new Set()
|
||||||
for (const option of accountOptionMap.values()) {
|
for (const option of accountOptionMap.values()) {
|
||||||
const info = await resolveAccountInfo(option.id, option.accountType)
|
const info = await resolveAccountInfo(option.id, option.accountType)
|
||||||
if (info && info.name) {
|
if (info && info.name) {
|
||||||
|
if (accountIdAdded.has(option.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
accountIdAdded.add(option.id)
|
||||||
accountOptions.push({
|
accountOptions.push({
|
||||||
id: option.id,
|
id: option.id,
|
||||||
name: info.name,
|
name: info.name,
|
||||||
|
|||||||
@@ -44,12 +44,20 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div class="flex items-center gap-2">
|
||||||
class="flex h-10 w-10 items-center justify-center rounded-full bg-gray-100 text-gray-500 transition hover:bg-gray-200 hover:text-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200"
|
<button
|
||||||
@click="handleClose"
|
class="flex items-center gap-2 rounded-full bg-purple-100 px-3 py-2 text-xs font-semibold text-purple-700 transition hover:bg-purple-200 dark:bg-purple-500/10 dark:text-purple-200 dark:hover:bg-purple-500/20"
|
||||||
>
|
@click="goTimeline"
|
||||||
<i class="fas fa-times" />
|
>
|
||||||
</button>
|
<i class="fas fa-clock" /> 请求时间线
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-gray-100 text-gray-500 transition hover:bg-gray-200 hover:text-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200"
|
||||||
|
@click="handleClose"
|
||||||
|
>
|
||||||
|
<i class="fas fa-times" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
@@ -325,6 +333,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import Chart from 'chart.js/auto'
|
import Chart from 'chart.js/auto'
|
||||||
import { useThemeStore } from '@/stores/theme'
|
import { useThemeStore } from '@/stores/theme'
|
||||||
@@ -343,6 +352,7 @@ const emit = defineEmits(['close'])
|
|||||||
|
|
||||||
const themeStore = useThemeStore()
|
const themeStore = useThemeStore()
|
||||||
const { isDarkMode } = storeToRefs(themeStore)
|
const { isDarkMode } = storeToRefs(themeStore)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const chartCanvas = ref(null)
|
const chartCanvas = ref(null)
|
||||||
let chartInstance = null
|
let chartInstance = null
|
||||||
@@ -579,6 +589,14 @@ const handleClose = () => {
|
|||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const goTimeline = () => {
|
||||||
|
if (!props.account?.id) return
|
||||||
|
router.push({
|
||||||
|
path: `/accounts/${props.account.id}/usage-records`,
|
||||||
|
query: { platform: props.account.platform || props.account.accountType }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
|
|||||||
@@ -77,7 +77,21 @@ const getActionClass = (action) => {
|
|||||||
return colorMap[action.color] || colorMap.gray
|
return colorMap[action.color] || colorMap.gray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const instanceId = Symbol('action-dropdown')
|
||||||
|
const handleGlobalOpen = (event) => {
|
||||||
|
if (event?.detail?.id !== instanceId) {
|
||||||
|
closeDropdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toggleDropdown = async () => {
|
const toggleDropdown = async () => {
|
||||||
|
if (!isOpen.value) {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('action-dropdown-open', {
|
||||||
|
detail: { id: instanceId }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
isOpen.value = !isOpen.value
|
isOpen.value = !isOpen.value
|
||||||
if (isOpen.value) {
|
if (isOpen.value) {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -164,11 +178,13 @@ onMounted(() => {
|
|||||||
window.addEventListener('scroll', handleScroll, true)
|
window.addEventListener('scroll', handleScroll, true)
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
document.addEventListener('click', handleClickOutside)
|
document.addEventListener('click', handleClickOutside)
|
||||||
|
window.addEventListener('action-dropdown-open', handleGlobalOpen)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('scroll', handleScroll, true)
|
window.removeEventListener('scroll', handleScroll, true)
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener('resize', handleResize)
|
||||||
document.removeEventListener('click', handleClickOutside)
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
window.removeEventListener('action-dropdown-open', handleGlobalOpen)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1199,15 +1199,6 @@
|
|||||||
<i class="fas fa-chart-line" />
|
<i class="fas fa-chart-line" />
|
||||||
<span class="ml-1">详情</span>
|
<span class="ml-1">详情</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
v-if="canViewUsage(account)"
|
|
||||||
class="rounded bg-purple-100 px-2.5 py-1 text-xs font-medium text-purple-700 transition-colors hover:bg-purple-200"
|
|
||||||
title="请求时间线"
|
|
||||||
@click="viewAccountTimeline(account)"
|
|
||||||
>
|
|
||||||
<i class="fas fa-clock" />
|
|
||||||
<span class="ml-1">时间线</span>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
v-if="canTestAccount(account)"
|
v-if="canTestAccount(account)"
|
||||||
class="rounded bg-cyan-100 px-2.5 py-1 text-xs font-medium text-cyan-700 transition-colors hover:bg-cyan-200 dark:bg-cyan-900/40 dark:text-cyan-300 dark:hover:bg-cyan-800/50"
|
class="rounded bg-cyan-100 px-2.5 py-1 text-xs font-medium text-cyan-700 transition-colors hover:bg-cyan-200 dark:bg-cyan-900/40 dark:text-cyan-300 dark:hover:bg-cyan-800/50"
|
||||||
@@ -1677,15 +1668,6 @@
|
|||||||
<i class="fas fa-chart-line" />
|
<i class="fas fa-chart-line" />
|
||||||
详情
|
详情
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
v-if="canViewUsage(account)"
|
|
||||||
class="flex flex-1 items-center justify-center gap-1 rounded-lg bg-purple-50 px-3 py-2 text-xs text-purple-600 transition-colors hover:bg-purple-100"
|
|
||||||
@click="viewAccountTimeline(account)"
|
|
||||||
>
|
|
||||||
<i class="fas fa-clock" />
|
|
||||||
时间线
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="canTestAccount(account)"
|
v-if="canTestAccount(account)"
|
||||||
class="flex flex-1 items-center justify-center gap-1 rounded-lg bg-cyan-50 px-3 py-2 text-xs text-cyan-600 transition-colors hover:bg-cyan-100 dark:bg-cyan-900/40 dark:text-cyan-300 dark:hover:bg-cyan-800/50"
|
class="flex flex-1 items-center justify-center gap-1 rounded-lg bg-cyan-50 px-3 py-2 text-xs text-cyan-600 transition-colors hover:bg-cyan-100 dark:bg-cyan-900/40 dark:text-cyan-300 dark:hover:bg-cyan-800/50"
|
||||||
@@ -1872,7 +1854,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { showToast } from '@/utils/toast'
|
import { showToast } from '@/utils/toast'
|
||||||
import { apiClient } from '@/config/api'
|
import { apiClient } from '@/config/api'
|
||||||
import { useConfirm } from '@/composables/useConfirm'
|
import { useConfirm } from '@/composables/useConfirm'
|
||||||
@@ -1887,7 +1868,6 @@ import ActionDropdown from '@/components/common/ActionDropdown.vue'
|
|||||||
|
|
||||||
// 使用确认弹窗
|
// 使用确认弹窗
|
||||||
const { showConfirmModal, confirmOptions, showConfirm, handleConfirm, handleCancel } = useConfirm()
|
const { showConfirmModal, confirmOptions, showConfirm, handleConfirm, handleCancel } = useConfirm()
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
// 数据状态
|
// 数据状态
|
||||||
const accounts = ref([])
|
const accounts = ref([])
|
||||||
@@ -2119,13 +2099,6 @@ const getAccountActions = (account) => {
|
|||||||
color: 'indigo',
|
color: 'indigo',
|
||||||
handler: () => openAccountUsageModal(account)
|
handler: () => openAccountUsageModal(account)
|
||||||
})
|
})
|
||||||
actions.push({
|
|
||||||
key: 'timeline',
|
|
||||||
label: '请求时间线',
|
|
||||||
icon: 'fa-clock',
|
|
||||||
color: 'purple',
|
|
||||||
handler: () => viewAccountTimeline(account)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 测试账户
|
// 测试账户
|
||||||
@@ -2186,13 +2159,6 @@ const openAccountUsageModal = async (account) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewAccountTimeline = (account) => {
|
|
||||||
router.push({
|
|
||||||
path: `/accounts/${account.id}/usage-records`,
|
|
||||||
query: { platform: account.platform || account.accountType }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeAccountUsageModal = () => {
|
const closeAccountUsageModal = () => {
|
||||||
showAccountUsageModal.value = false
|
showAccountUsageModal.value = false
|
||||||
accountUsageLoading.value = false
|
accountUsageLoading.value = false
|
||||||
|
|||||||
Reference in New Issue
Block a user