diff --git a/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts b/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts index d094f5366d..31f6e3bd26 100644 --- a/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts +++ b/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts @@ -107,7 +107,7 @@ describe('BulkEditAccountModal', () => { expect(mappingTab).toBeTruthy() await mappingTab!.trigger('click') - expect(wrapper.text()).toContain('3.1-Flash-Image passthrough') + expect(wrapper.text()).toContain('3.1-Flash-Image透传') expect(wrapper.text()).toContain('3-Pro-Image→3.1') expect(wrapper.text()).not.toContain('GPT-5.3 Codex Spark') }) diff --git a/frontend/src/views/admin/GroupsView.vue b/frontend/src/views/admin/GroupsView.vue index 0700f127b8..404ca0dc64 100644 --- a/frontend/src/views/admin/GroupsView.vue +++ b/frontend/src/views/admin/GroupsView.vue @@ -168,20 +168,40 @@
- +
{{ t("admin.groups.subscription.noLimit") }} +
+ {{ t("admin.groups.usageTotal") }} + {{ + usageLoading + ? "—" + : formatUsd(usageMap.get(row.id)?.total_cost ?? 0) + }} +
@@ -3396,7 +3426,9 @@ const saveColumnsToStorage = () => { }; const isColumnVisible = (key: string) => !hiddenColumns.has(key); -const hasVisibleUsageColumn = computed(() => isColumnVisible("usage")); +const hasVisibleUsageSummaryConsumer = computed( + () => isColumnVisible("usage") || isColumnVisible("billing_type"), +); const hasVisibleCapacityColumn = computed(() => isColumnVisible("capacity")); const toggleColumn = (key: string) => { @@ -3411,7 +3443,7 @@ const toggleColumn = (key: string) => { } saveColumnsToStorage(); - if (wasHidden && key === "usage") { + if (wasHidden && (key === "usage" || key === "billing_type")) { loadUsageSummary(); } if (wasHidden && key === "capacity") { @@ -3571,9 +3603,12 @@ const copyAccountsGroupOptionsForEdit = computed(() => { const groups = ref([]); const loading = ref(false); -const usageMap = ref>( - new Map(), -); +type GroupUsageSummary = { + today_cost: number; + total_cost: number; +}; + +const usageMap = ref>(new Map()); const usageLoading = ref(false); const capacityMap = ref< Map< @@ -4146,7 +4181,7 @@ const loadGroups = async () => { groups.value = response.items; pagination.total = response.total; pagination.pages = response.pages; - if (hasVisibleUsageColumn.value) { + if (hasVisibleUsageSummaryConsumer.value) { loadUsageSummary(); } else { usageLoading.value = false; @@ -4177,8 +4212,28 @@ const formatCost = (cost: number): string => { return cost.toFixed(2); }; +const formatUsd = (cost: number | null | undefined): string => + `$${formatCost(cost ?? 0)}`; + +const getQuotaUsageClass = ( + used: number, + limit: number | null | undefined, +): string => { + if (!limit || limit <= 0) { + return "font-medium text-gray-700 dark:text-gray-300"; + } + const ratio = used / limit; + if (ratio >= 1) { + return "font-semibold text-red-600 dark:text-red-400"; + } + if (ratio >= 0.8) { + return "font-semibold text-amber-600 dark:text-amber-400"; + } + return "font-medium text-gray-700 dark:text-gray-300"; +}; + const loadUsageSummary = async () => { - if (!hasVisibleUsageColumn.value) { + if (!hasVisibleUsageSummaryConsumer.value) { usageLoading.value = false; return; } @@ -4186,7 +4241,7 @@ const loadUsageSummary = async () => { try { const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; const data = await adminAPI.groups.getUsageSummary(tz); - const map = new Map(); + const map = new Map(); for (const item of data) { map.set(item.group_id, { today_cost: item.today_cost, diff --git a/frontend/src/views/admin/__tests__/DashboardView.spec.ts b/frontend/src/views/admin/__tests__/DashboardView.spec.ts index 7cb5123211..ded7db034f 100644 --- a/frontend/src/views/admin/__tests__/DashboardView.spec.ts +++ b/frontend/src/views/admin/__tests__/DashboardView.spec.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { flushPromises, mount } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' import type { DashboardStats } from '@/types' import DashboardView from '../DashboardView.vue' @@ -87,6 +88,8 @@ const createDashboardStats = (): DashboardStats => ({ describe('admin DashboardView', () => { beforeEach(() => { + setActivePinia(createPinia()) + getSnapshotV2.mockReset() getUserUsageTrend.mockReset() getUserSpendingRanking.mockReset() diff --git a/frontend/src/views/admin/__tests__/GroupsView.columnSettings.spec.ts b/frontend/src/views/admin/__tests__/GroupsView.columnSettings.spec.ts index c323d44abd..e634bdd5d7 100644 --- a/frontend/src/views/admin/__tests__/GroupsView.columnSettings.spec.ts +++ b/frontend/src/views/admin/__tests__/GroupsView.columnSettings.spec.ts @@ -308,8 +308,11 @@ describe('admin GroupsView column settings', () => { expect(localStorage.getItem('group-hidden-columns')).toBe(JSON.stringify(['usage'])) }) - it('skips hidden usage and capacity fetches until those columns are shown', async () => { - localStorage.setItem('group-hidden-columns', JSON.stringify(['usage', 'capacity'])) + it('skips usage and capacity fetches until consuming columns are shown', async () => { + localStorage.setItem( + 'group-hidden-columns', + JSON.stringify(['billing_type', 'usage', 'capacity']), + ) const wrapper = await mountView()