feat: 优化 OAuth 账号导入流程
This commit is contained in:
@@ -14,7 +14,6 @@
|
||||
<AccountTableActions
|
||||
:loading="loading"
|
||||
@refresh="handleManualRefresh"
|
||||
@sync="showSync = true"
|
||||
@create="showCreate = true"
|
||||
>
|
||||
<template #after>
|
||||
@@ -23,7 +22,7 @@
|
||||
<button
|
||||
@click="
|
||||
showAutoRefreshDropdown = !showAutoRefreshDropdown;
|
||||
showColumnDropdown = false
|
||||
showAccountToolsDropdown = false
|
||||
"
|
||||
class="btn btn-secondary px-2 md:px-3"
|
||||
:title="t('admin.accounts.autoRefresh')"
|
||||
@@ -63,68 +62,100 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Passthrough Rules -->
|
||||
<button
|
||||
@click="showErrorPassthrough = true"
|
||||
class="btn btn-secondary"
|
||||
:title="t('admin.errorPassthrough.title')"
|
||||
>
|
||||
<Icon name="shield" size="md" class="mr-1.5" />
|
||||
<span class="hidden md:inline">{{ t('admin.errorPassthrough.title') }}</span>
|
||||
</button>
|
||||
|
||||
<!-- TLS Fingerprint Profiles -->
|
||||
<button
|
||||
@click="showTLSFingerprintProfiles = true"
|
||||
class="btn btn-secondary"
|
||||
:title="t('admin.tlsFingerprintProfiles.title')"
|
||||
>
|
||||
<Icon name="lock" size="md" class="mr-1.5" />
|
||||
<span class="hidden md:inline">{{ t('admin.tlsFingerprintProfiles.title') }}</span>
|
||||
</button>
|
||||
|
||||
<!-- Column Settings Dropdown -->
|
||||
<div class="relative" ref="columnDropdownRef">
|
||||
<!-- More Tools Dropdown -->
|
||||
<div class="relative" ref="accountToolsDropdownRef">
|
||||
<button
|
||||
@click="
|
||||
showColumnDropdown = !showColumnDropdown;
|
||||
showAccountToolsDropdown = !showAccountToolsDropdown;
|
||||
showAutoRefreshDropdown = false
|
||||
"
|
||||
class="btn btn-secondary px-2 md:px-3"
|
||||
:title="t('admin.users.columnSettings')"
|
||||
:title="t('admin.accounts.moreActions')"
|
||||
>
|
||||
<svg class="h-4 w-4 md:mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 4.5v15m6-15v15m-10.875 0h15.75c.621 0 1.125-.504 1.125-1.125V5.625c0-.621-.504-1.125-1.125-1.125H4.125C3.504 4.5 3 5.004 3 5.625v12.75c0 .621.504 1.125 1.125 1.125z" />
|
||||
</svg>
|
||||
<span class="hidden md:inline">{{ t('admin.users.columnSettings') }}</span>
|
||||
<Icon name="more" size="sm" class="md:mr-1.5" />
|
||||
<span class="hidden md:inline">{{ t('admin.accounts.moreActions') }}</span>
|
||||
<Icon name="chevronDown" size="xs" class="ml-1 hidden md:inline" />
|
||||
</button>
|
||||
<!-- Dropdown menu -->
|
||||
<div
|
||||
v-if="showColumnDropdown"
|
||||
class="absolute right-0 z-50 mt-2 w-48 origin-top-right rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800"
|
||||
v-if="showAccountToolsDropdown"
|
||||
class="absolute right-0 z-50 mt-2 w-[min(20rem,calc(100vw-2rem))] origin-top-right overflow-hidden rounded-lg border border-gray-200 bg-white shadow-xl dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="max-h-80 overflow-y-auto p-2">
|
||||
<button
|
||||
v-for="col in toggleableColumns"
|
||||
:key="col.key"
|
||||
@click="toggleColumn(col.key)"
|
||||
class="flex w-full items-center justify-between rounded-md px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
>
|
||||
<span>{{ col.label }}</span>
|
||||
<Icon v-if="isColumnVisible(col.key)" name="check" size="sm" class="text-primary-500" />
|
||||
<div class="max-h-[70vh] overflow-y-auto p-2">
|
||||
<div class="px-2 py-2">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500">
|
||||
{{ t('admin.accounts.dataActions') }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="account-tools-menu-item" @click="openSyncFromCrs">
|
||||
<span class="account-tools-menu-icon bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-300">
|
||||
<Icon name="sync" size="sm" />
|
||||
</span>
|
||||
<span class="flex-1 text-left">{{ t('admin.accounts.syncFromCrs') }}</span>
|
||||
</button>
|
||||
<button class="account-tools-menu-item" @click="openImportData">
|
||||
<span class="account-tools-menu-icon bg-emerald-50 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-300">
|
||||
<Icon name="upload" size="sm" />
|
||||
</span>
|
||||
<span class="flex-1 text-left">{{ t('admin.accounts.dataImport') }}</span>
|
||||
</button>
|
||||
<button class="account-tools-menu-item" @click="openExportDataDialogFromMenu">
|
||||
<span class="account-tools-menu-icon bg-violet-50 text-violet-600 dark:bg-violet-900/30 dark:text-violet-300">
|
||||
<Icon name="download" size="sm" />
|
||||
</span>
|
||||
<span class="flex-1 text-left">
|
||||
{{ selIds.length ? t('admin.accounts.dataExportSelected') : t('admin.accounts.dataExport') }}
|
||||
</span>
|
||||
<span
|
||||
v-if="selIds.length"
|
||||
class="rounded-full bg-primary-100 px-2 py-0.5 text-xs font-medium text-primary-700 dark:bg-primary-900/40 dark:text-primary-300"
|
||||
>
|
||||
{{ t('admin.accounts.selectedCount', { count: selIds.length }) }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="my-2 border-t border-gray-100 dark:border-gray-700"></div>
|
||||
<div class="px-2 py-2">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500">
|
||||
{{ t('admin.accounts.toolActions') }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="account-tools-menu-item" @click="openErrorPassthrough">
|
||||
<span class="account-tools-menu-icon bg-amber-50 text-amber-600 dark:bg-amber-900/30 dark:text-amber-300">
|
||||
<Icon name="shield" size="sm" />
|
||||
</span>
|
||||
<span class="flex-1 text-left">{{ t('admin.errorPassthrough.title') }}</span>
|
||||
</button>
|
||||
<button class="account-tools-menu-item" @click="openTLSFingerprintProfiles">
|
||||
<span class="account-tools-menu-icon bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-200">
|
||||
<Icon name="lock" size="sm" />
|
||||
</span>
|
||||
<span class="flex-1 text-left">{{ t('admin.tlsFingerprintProfiles.title') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="my-2 border-t border-gray-100 dark:border-gray-700"></div>
|
||||
<div class="px-2 py-2">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500">
|
||||
{{ t('admin.accounts.viewColumns') }}
|
||||
</span>
|
||||
<Icon name="grid" size="sm" class="text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-1">
|
||||
<button
|
||||
v-for="col in toggleableColumns"
|
||||
:key="col.key"
|
||||
@click="toggleColumn(col.key)"
|
||||
class="flex w-full items-center justify-between rounded-md px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
>
|
||||
<span class="truncate">{{ col.label }}</span>
|
||||
<Icon v-if="isColumnVisible(col.key)" name="check" size="sm" class="text-primary-500" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #beforeCreate>
|
||||
<button @click="showImportData = true" class="btn btn-secondary">
|
||||
{{ t('admin.accounts.dataImport') }}
|
||||
</button>
|
||||
<button @click="openExportDataDialog" class="btn btn-secondary">
|
||||
{{ selIds.length ? t('admin.accounts.dataExportSelected') : t('admin.accounts.dataExport') }}
|
||||
</button>
|
||||
</template>
|
||||
</AccountTableActions>
|
||||
</div>
|
||||
<div
|
||||
@@ -457,9 +488,9 @@ const togglingSchedulable = ref<number | null>(null)
|
||||
const menu = reactive<{show:boolean, acc:Account|null, pos:{top:number, left:number}|null}>({ show: false, acc: null, pos: null })
|
||||
const exportingData = ref(false)
|
||||
|
||||
// Column settings
|
||||
const showColumnDropdown = ref(false)
|
||||
const columnDropdownRef = ref<HTMLElement | null>(null)
|
||||
// Account tools dropdown
|
||||
const showAccountToolsDropdown = ref(false)
|
||||
const accountToolsDropdownRef = ref<HTMLElement | null>(null)
|
||||
const hiddenColumns = reactive<Set<string>>(new Set())
|
||||
const DEFAULT_HIDDEN_COLUMNS = ['today_stats', 'proxy', 'notes', 'priority', 'rate_multiplier']
|
||||
const HIDDEN_COLUMNS_KEY = 'account-hidden-columns'
|
||||
@@ -820,7 +851,8 @@ const isAnyModalOpen = computed(() => {
|
||||
showTest.value ||
|
||||
showStats.value ||
|
||||
showSchedulePanel.value ||
|
||||
showErrorPassthrough.value
|
||||
showErrorPassthrough.value ||
|
||||
showTLSFingerprintProfiles.value
|
||||
)
|
||||
})
|
||||
|
||||
@@ -931,6 +963,35 @@ const handleManualRefresh = async () => {
|
||||
usageManualRefreshToken.value += 1
|
||||
}
|
||||
|
||||
const closeAccountToolsDropdown = () => {
|
||||
showAccountToolsDropdown.value = false
|
||||
}
|
||||
|
||||
const openSyncFromCrs = () => {
|
||||
closeAccountToolsDropdown()
|
||||
showSync.value = true
|
||||
}
|
||||
|
||||
const openImportData = () => {
|
||||
closeAccountToolsDropdown()
|
||||
showImportData.value = true
|
||||
}
|
||||
|
||||
const openExportDataDialogFromMenu = () => {
|
||||
closeAccountToolsDropdown()
|
||||
openExportDataDialog()
|
||||
}
|
||||
|
||||
const openErrorPassthrough = () => {
|
||||
closeAccountToolsDropdown()
|
||||
showErrorPassthrough.value = true
|
||||
}
|
||||
|
||||
const openTLSFingerprintProfiles = () => {
|
||||
closeAccountToolsDropdown()
|
||||
showTLSFingerprintProfiles.value = true
|
||||
}
|
||||
|
||||
const syncPendingListChanges = async () => {
|
||||
hasPendingListSync.value = false
|
||||
await load()
|
||||
@@ -944,7 +1005,7 @@ const { pause: pauseAutoRefresh, resume: resumeAutoRefresh } = useIntervalFn(
|
||||
if (document.hidden) return
|
||||
if (loading.value || autoRefreshFetching.value) return
|
||||
if (isAnyModalOpen.value) return
|
||||
if (menu.show) return
|
||||
if (menu.show || showAccountToolsDropdown.value || showAutoRefreshDropdown.value) return
|
||||
if (inAutoRefreshSilentWindow()) {
|
||||
autoRefreshCountdown.value = Math.max(
|
||||
0,
|
||||
@@ -1572,11 +1633,11 @@ const handleScroll = () => {
|
||||
menu.show = false
|
||||
}
|
||||
|
||||
// 点击外部关闭列设置下拉菜单
|
||||
// 点击外部关闭顶部下拉菜单
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement
|
||||
if (columnDropdownRef.value && !columnDropdownRef.value.contains(target)) {
|
||||
showColumnDropdown.value = false
|
||||
if (accountToolsDropdownRef.value && !accountToolsDropdownRef.value.contains(target)) {
|
||||
showAccountToolsDropdown.value = false
|
||||
}
|
||||
if (autoRefreshDropdownRef.value && !autoRefreshDropdownRef.value.contains(target)) {
|
||||
showAutoRefreshDropdown.value = false
|
||||
@@ -1608,3 +1669,13 @@ onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.account-tools-menu-item {
|
||||
@apply flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700;
|
||||
}
|
||||
|
||||
.account-tools-menu-icon {
|
||||
@apply inline-flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user