fix: 账户时间线入口与路由修复

- 移除账户列表下拉/卡片的时间线入口,仅保留详情弹窗顶部按钮
  - ActionDropdown 全局互斥,避免多菜单堆叠
  - 账户筛选去重,避免“未知渠道”重复泄露
This commit is contained in:
atoz03
2025-12-05 14:57:34 +08:00
parent ff30bfab82
commit 9b0d0bee96
4 changed files with 45 additions and 40 deletions

View File

@@ -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,

View File

@@ -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) => {

View File

@@ -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>

View File

@@ -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