Merge pull request #2895 from wucm667/feat/usage-window-tooltip
feat(admin): 账号用量窗口 5h/7d 增加说明 tooltip
This commit is contained in:
commit
ed8f5666c6
@ -3105,6 +3105,7 @@ export default {
|
|||||||
expiresAt: 'Expires At',
|
expiresAt: 'Expires At',
|
||||||
actions: 'Actions'
|
actions: 'Actions'
|
||||||
},
|
},
|
||||||
|
usageWindowsHint: '"5h / 7d" are the upstream account\'s official rolling usage windows (e.g. OpenAI ChatGPT, Claude). They are imposed by the upstream provider on the account itself — not configured by sub2api, and unrelated to the models you map. Usage resets automatically once each window rolls over, and the limit cannot be lifted from within sub2api.',
|
||||||
allPrivacyModes: 'All Privacy States',
|
allPrivacyModes: 'All Privacy States',
|
||||||
privacyUnset: 'Unset',
|
privacyUnset: 'Unset',
|
||||||
privacyTrainingOff: 'Training data sharing disabled',
|
privacyTrainingOff: 'Training data sharing disabled',
|
||||||
|
|||||||
@ -3143,6 +3143,7 @@ export default {
|
|||||||
expiresAt: '过期时间',
|
expiresAt: '过期时间',
|
||||||
actions: '操作'
|
actions: '操作'
|
||||||
},
|
},
|
||||||
|
usageWindowsHint: '“5h / 7d”是上游账号(如 OpenAI ChatGPT、Claude)官方的滚动用量窗口限制,由上游对账号设定,并非 sub2api 配置,也与你映射的模型无关。窗口滚动到期后用量会自动重置,无法在 sub2api 端解除该限制。',
|
||||||
allPrivacyModes: '全部Privacy状态',
|
allPrivacyModes: '全部Privacy状态',
|
||||||
privacyUnset: '未设置',
|
privacyUnset: '未设置',
|
||||||
privacyTrainingOff: '已关闭训练数据共享',
|
privacyTrainingOff: '已关闭训练数据共享',
|
||||||
|
|||||||
@ -273,6 +273,12 @@
|
|||||||
<template #cell-groups="{ row }">
|
<template #cell-groups="{ row }">
|
||||||
<AccountGroupsCell :groups="row.groups" :max-display="4" />
|
<AccountGroupsCell :groups="row.groups" :max-display="4" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #header-usage="{ column }">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span>{{ column.label }}</span>
|
||||||
|
<HelpTooltip :content="t('admin.accounts.usageWindowsHint')" width-class="w-72" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template #cell-usage="{ row }">
|
<template #cell-usage="{ row }">
|
||||||
<AccountUsageCell
|
<AccountUsageCell
|
||||||
:account="row"
|
:account="row"
|
||||||
@ -390,6 +396,7 @@ import { useTableSelection } from '@/composables/useTableSelection'
|
|||||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||||
import TablePageLayout from '@/components/layout/TablePageLayout.vue'
|
import TablePageLayout from '@/components/layout/TablePageLayout.vue'
|
||||||
import DataTable from '@/components/common/DataTable.vue'
|
import DataTable from '@/components/common/DataTable.vue'
|
||||||
|
import HelpTooltip from '@/components/common/HelpTooltip.vue'
|
||||||
import Pagination from '@/components/common/Pagination.vue'
|
import Pagination from '@/components/common/Pagination.vue'
|
||||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||||
import { CreateAccountModal, EditAccountModal, BulkEditAccountModal, SyncFromCrsModal, TempUnschedStatusModal } from '@/components/account'
|
import { CreateAccountModal, EditAccountModal, BulkEditAccountModal, SyncFromCrsModal, TempUnschedStatusModal } from '@/components/account'
|
||||||
|
|||||||
@ -0,0 +1,164 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { flushPromises, mount } from '@vue/test-utils'
|
||||||
|
|
||||||
|
import AccountsView from '../AccountsView.vue'
|
||||||
|
|
||||||
|
const {
|
||||||
|
listAccounts,
|
||||||
|
listWithEtag,
|
||||||
|
getBatchTodayStats,
|
||||||
|
getAllProxies,
|
||||||
|
getAllGroups
|
||||||
|
} = vi.hoisted(() => ({
|
||||||
|
listAccounts: vi.fn(),
|
||||||
|
listWithEtag: vi.fn(),
|
||||||
|
getBatchTodayStats: vi.fn(),
|
||||||
|
getAllProxies: vi.fn(),
|
||||||
|
getAllGroups: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/api/admin', () => ({
|
||||||
|
adminAPI: {
|
||||||
|
accounts: {
|
||||||
|
list: listAccounts,
|
||||||
|
listWithEtag,
|
||||||
|
getBatchTodayStats,
|
||||||
|
delete: vi.fn(),
|
||||||
|
batchClearError: vi.fn(),
|
||||||
|
batchRefresh: vi.fn(),
|
||||||
|
toggleSchedulable: vi.fn()
|
||||||
|
},
|
||||||
|
proxies: {
|
||||||
|
getAll: getAllProxies
|
||||||
|
},
|
||||||
|
groups: {
|
||||||
|
getAll: getAllGroups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/stores/app', () => ({
|
||||||
|
useAppStore: () => ({
|
||||||
|
showError: vi.fn(),
|
||||||
|
showSuccess: vi.fn(),
|
||||||
|
showInfo: vi.fn()
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/stores/auth', () => ({
|
||||||
|
useAuthStore: () => ({
|
||||||
|
token: 'test-token'
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('vue-i18n', async () => {
|
||||||
|
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useI18n: () => ({
|
||||||
|
t: (key: string) => key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Render the per-column header slots so we can assert the usage-window header hint.
|
||||||
|
const DataTableStub = {
|
||||||
|
props: ['columns', 'data'],
|
||||||
|
template: `
|
||||||
|
<div data-test="data-table">
|
||||||
|
<template v-for="column in columns" :key="column.key">
|
||||||
|
<div v-if="column.key === 'usage'" data-test="usage-header">
|
||||||
|
<slot :name="'header-' + column.key" :column="column" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose the content passed to HelpTooltip without dealing with its <Teleport>.
|
||||||
|
const HelpTooltipStub = {
|
||||||
|
props: ['content', 'widthClass'],
|
||||||
|
template: '<span data-test="usage-windows-hint">{{ content }}</span>'
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountView() {
|
||||||
|
return mount(AccountsView, {
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
AppLayout: { template: '<div><slot /></div>' },
|
||||||
|
TablePageLayout: {
|
||||||
|
template: '<div><slot name="filters" /><slot name="table" /><slot name="pagination" /></div>'
|
||||||
|
},
|
||||||
|
DataTable: DataTableStub,
|
||||||
|
HelpTooltip: HelpTooltipStub,
|
||||||
|
Pagination: true,
|
||||||
|
ConfirmDialog: true,
|
||||||
|
AccountTableActions: { template: '<div><slot name="beforeCreate" /><slot name="after" /></div>' },
|
||||||
|
AccountTableFilters: { template: '<div></div>' },
|
||||||
|
AccountBulkActionsBar: true,
|
||||||
|
AccountActionMenu: true,
|
||||||
|
ImportDataModal: true,
|
||||||
|
ReAuthAccountModal: true,
|
||||||
|
AccountTestModal: true,
|
||||||
|
AccountStatsModal: true,
|
||||||
|
ScheduledTestsPanel: true,
|
||||||
|
SyncFromCrsModal: true,
|
||||||
|
TempUnschedStatusModal: true,
|
||||||
|
ErrorPassthroughRulesModal: true,
|
||||||
|
TLSFingerprintProfilesModal: true,
|
||||||
|
CreateAccountModal: true,
|
||||||
|
EditAccountModal: true,
|
||||||
|
BulkEditAccountModal: true,
|
||||||
|
PlatformTypeBadge: true,
|
||||||
|
AccountCapacityCell: true,
|
||||||
|
AccountStatusIndicator: true,
|
||||||
|
AccountTodayStatsCell: true,
|
||||||
|
AccountGroupsCell: true,
|
||||||
|
AccountUsageCell: true,
|
||||||
|
Icon: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('admin AccountsView usage windows hint', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear()
|
||||||
|
|
||||||
|
listAccounts.mockReset()
|
||||||
|
listWithEtag.mockReset()
|
||||||
|
getBatchTodayStats.mockReset()
|
||||||
|
getAllProxies.mockReset()
|
||||||
|
getAllGroups.mockReset()
|
||||||
|
|
||||||
|
listAccounts.mockResolvedValue({
|
||||||
|
items: [],
|
||||||
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
pages: 0
|
||||||
|
})
|
||||||
|
listWithEtag.mockResolvedValue({
|
||||||
|
notModified: true,
|
||||||
|
etag: null,
|
||||||
|
data: null
|
||||||
|
})
|
||||||
|
getBatchTodayStats.mockResolvedValue({ stats: {} })
|
||||||
|
getAllProxies.mockResolvedValue([])
|
||||||
|
getAllGroups.mockResolvedValue([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders an explanatory tooltip next to the usage windows column header', async () => {
|
||||||
|
const wrapper = mountView()
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const header = wrapper.find('[data-test="usage-header"]')
|
||||||
|
expect(header.exists()).toBe(true)
|
||||||
|
// Column label is still shown alongside the help icon.
|
||||||
|
expect(header.text()).toContain('admin.accounts.columns.usageWindows')
|
||||||
|
|
||||||
|
const hint = wrapper.find('[data-test="usage-windows-hint"]')
|
||||||
|
expect(hint.exists()).toBe(true)
|
||||||
|
expect(hint.text()).toBe('admin.accounts.usageWindowsHint')
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user