mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-31 16:26:30 +00:00
fix: hide batch image entry without allowed key
This commit is contained in:
@@ -195,6 +195,7 @@ import { useAdminSettingsStore, useAppStore, useAuthStore, useOnboardingStore }
|
||||
import VersionBadge from '@/components/common/VersionBadge.vue'
|
||||
import { sanitizeSvg } from '@/utils/sanitize'
|
||||
import { FeatureFlags, makeSidebarFlag } from '@/utils/featureFlags'
|
||||
import { useBatchImageAccess } from '@/composables/useBatchImageAccess'
|
||||
|
||||
interface NavItem {
|
||||
path: string
|
||||
@@ -240,6 +241,7 @@ const appStore = useAppStore()
|
||||
const authStore = useAuthStore()
|
||||
const onboardingStore = useOnboardingStore()
|
||||
const adminSettingsStore = useAdminSettingsStore()
|
||||
const { canUseBatchImage, refreshBatchImageAccess } = useBatchImageAccess()
|
||||
|
||||
const sidebarCollapsed = computed(() => appStore.sidebarCollapsed)
|
||||
const mobileOpen = computed(() => appStore.mobileOpen)
|
||||
@@ -683,6 +685,7 @@ const flagAffiliate = makeSidebarFlag(FeatureFlags.affiliate)
|
||||
const flagRiskControl = makeSidebarFlag(FeatureFlags.riskControl)
|
||||
const flagOpsMonitoring = () => adminSettingsStore.opsMonitoringEnabled
|
||||
const flagAdminPayment = () => adminSettingsStore.paymentEnabled
|
||||
const flagBatchImageAccess = () => canUseBatchImage.value
|
||||
|
||||
// buildSelfNavItems 构造用户自己的导航项(用户端主菜单和管理员的"我的账户"子菜单共享这组声明)。
|
||||
// withDashboard=true 时包含仪表盘(用户端),false 时不含(管理员的个人区已经有独立仪表盘入口)。
|
||||
@@ -696,7 +699,7 @@ function buildSelfNavItems(withDashboard: boolean): NavItem[] {
|
||||
}
|
||||
items.push(
|
||||
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
|
||||
{ path: '/batch-image', label: t('nav.batchImage'), icon: BatchImageIcon, hideInSimpleMode: true },
|
||||
{ path: '/batch-image', label: t('nav.batchImage'), icon: BatchImageIcon, hideInSimpleMode: true, featureFlag: flagBatchImageAccess },
|
||||
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon, hideInSimpleMode: true },
|
||||
{ path: '/available-channels', label: t('nav.availableChannels'), icon: ChannelIcon, hideInSimpleMode: true, featureFlag: flagAvailableChannels },
|
||||
{ path: '/monitor', label: t('nav.channelStatus'), icon: SignalIcon, featureFlag: flagChannelMonitor },
|
||||
@@ -917,6 +920,7 @@ watch(
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
void refreshBatchImageAccess()
|
||||
if (isAdmin.value) {
|
||||
adminSettingsStore.fetch()
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
/>
|
||||
</button>
|
||||
|
||||
<button @click="router.push('/batch-image')" class="group flex w-full items-center gap-4 rounded-xl bg-gray-50 p-4 text-left transition-all duration-200 hover:bg-gray-100 dark:bg-dark-800/50 dark:hover:bg-dark-800">
|
||||
<button v-if="canUseBatchImage" @click="router.push('/batch-image')" class="group flex w-full items-center gap-4 rounded-xl bg-gray-50 p-4 text-left transition-all duration-200 hover:bg-gray-100 dark:bg-dark-800/50 dark:hover:bg-dark-800">
|
||||
<div class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-xl bg-sky-100 transition-transform group-hover:scale-105 dark:bg-sky-900/30">
|
||||
<Icon name="sparkles" size="lg" class="text-sky-600 dark:text-sky-400" />
|
||||
</div>
|
||||
@@ -68,9 +68,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import { useBatchImageAccess } from '@/composables/useBatchImageAccess'
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const { canUseBatchImage, refreshBatchImageAccess } = useBatchImageAccess()
|
||||
|
||||
onMounted(() => {
|
||||
void refreshBatchImageAccess()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { keysAPI } from '@/api/keys'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import type { ApiKey } from '@/types'
|
||||
|
||||
const loaded = ref(false)
|
||||
const loading = ref(false)
|
||||
const hasAllowedBatchImageKey = ref(false)
|
||||
let pendingLoad: Promise<boolean> | null = null
|
||||
const pageSize = 100
|
||||
|
||||
function keyAllowsBatchImage(key: ApiKey): boolean {
|
||||
return (
|
||||
key.status === 'active' &&
|
||||
key.group?.platform === 'gemini' &&
|
||||
key.group?.allow_batch_image_generation === true
|
||||
)
|
||||
}
|
||||
|
||||
async function loadBatchImageAccess(force = false): Promise<boolean> {
|
||||
const authStore = useAuthStore()
|
||||
if (!authStore.isAuthenticated) {
|
||||
loaded.value = true
|
||||
hasAllowedBatchImageKey.value = false
|
||||
return false
|
||||
}
|
||||
|
||||
if (loaded.value && !force) {
|
||||
return hasAllowedBatchImageKey.value
|
||||
}
|
||||
|
||||
if (pendingLoad && !force) {
|
||||
return pendingLoad
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
pendingLoad = (async () => {
|
||||
let page = 1
|
||||
while (true) {
|
||||
const response = await keysAPI.list(page, pageSize, {
|
||||
status: 'active',
|
||||
sort_by: 'created_at',
|
||||
sort_order: 'desc'
|
||||
})
|
||||
|
||||
if ((response.items || []).some(keyAllowsBatchImage)) {
|
||||
hasAllowedBatchImageKey.value = true
|
||||
loaded.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
if (page >= response.pages || (response.items || []).length === 0) {
|
||||
hasAllowedBatchImageKey.value = false
|
||||
loaded.value = true
|
||||
return false
|
||||
}
|
||||
|
||||
page += 1
|
||||
}
|
||||
})()
|
||||
.catch(() => {
|
||||
hasAllowedBatchImageKey.value = false
|
||||
loaded.value = true
|
||||
return false
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
pendingLoad = null
|
||||
})
|
||||
|
||||
return pendingLoad
|
||||
}
|
||||
|
||||
export function useBatchImageAccess() {
|
||||
const canUseBatchImage = computed(() => hasAllowedBatchImageKey.value)
|
||||
|
||||
return {
|
||||
canUseBatchImage,
|
||||
batchImageAccessLoaded: computed(() => loaded.value),
|
||||
batchImageAccessLoading: computed(() => loading.value),
|
||||
refreshBatchImageAccess: loadBatchImageAccess,
|
||||
}
|
||||
}
|
||||
@@ -225,6 +225,7 @@
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<button
|
||||
v-if="canUseBatchImage"
|
||||
type="button"
|
||||
class="group flex items-center gap-3 rounded-lg bg-gray-50 p-3 text-left transition-colors hover:bg-sky-50 dark:bg-dark-800/50 dark:hover:bg-sky-900/20"
|
||||
@click="router.push('/batch-image')"
|
||||
@@ -361,6 +362,7 @@ import DateRangePicker from '@/components/common/DateRangePicker.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import ModelDistributionChart from '@/components/charts/ModelDistributionChart.vue'
|
||||
import TokenUsageTrend from '@/components/charts/TokenUsageTrend.vue'
|
||||
import { useBatchImageAccess } from '@/composables/useBatchImageAccess'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
@@ -387,6 +389,7 @@ ChartJS.register(
|
||||
|
||||
const appStore = useAppStore()
|
||||
const router = useRouter()
|
||||
const { canUseBatchImage, refreshBatchImageAccess } = useBatchImageAccess()
|
||||
const stats = ref<DashboardStats | null>(null)
|
||||
const loading = ref(false)
|
||||
const chartsLoading = ref(false)
|
||||
@@ -746,6 +749,7 @@ const loadChartData = async () => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void refreshBatchImageAccess()
|
||||
loadDashboardStats()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user