style(frontend): 优化 Components 代码风格和结构
- 统一移除语句末尾分号,规范代码格式 - 优化组件类型定义和 props 声明 - 改进组件文档和示例代码 - 提升代码可读性和一致性
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
</span>
|
||||
<span class="select-icon">
|
||||
<svg
|
||||
:class="['w-5 h-5 transition-transform duration-200', isOpen && 'rotate-180']"
|
||||
:class="['h-5 w-5 transition-transform duration-200', isOpen && 'rotate-180']"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -27,15 +27,22 @@
|
||||
</button>
|
||||
|
||||
<Transition name="select-dropdown">
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="select-dropdown"
|
||||
>
|
||||
<div v-if="isOpen" class="select-dropdown">
|
||||
<!-- Search and Batch Test Header -->
|
||||
<div class="select-header">
|
||||
<div class="select-search">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
|
||||
<svg
|
||||
class="h-4 w-4 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
|
||||
/>
|
||||
</svg>
|
||||
<input
|
||||
ref="searchInputRef"
|
||||
@@ -54,12 +61,34 @@
|
||||
class="batch-test-btn"
|
||||
:title="t('admin.proxies.batchTest')"
|
||||
>
|
||||
<svg v-if="batchTesting" class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
<svg v-if="batchTesting" class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 010 1.972l-11.54 6.347a1.125 1.125 0 01-1.667-.986V5.653z" />
|
||||
<svg
|
||||
v-else
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 010 1.972l-11.54 6.347a1.125 1.125 0 01-1.667-.986V5.653z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -69,15 +98,12 @@
|
||||
<!-- No Proxy option -->
|
||||
<div
|
||||
@click="selectOption(null)"
|
||||
:class="[
|
||||
'select-option',
|
||||
modelValue === null && 'select-option-selected'
|
||||
]"
|
||||
:class="['select-option', modelValue === null && 'select-option-selected']"
|
||||
>
|
||||
<span class="select-option-label">{{ t('admin.accounts.noProxy') }}</span>
|
||||
<svg
|
||||
v-if="modelValue === null"
|
||||
class="w-4 h-4 text-primary-500"
|
||||
class="h-4 w-4 text-primary-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -92,18 +118,15 @@
|
||||
v-for="proxy in filteredProxies"
|
||||
:key="proxy.id"
|
||||
@click="selectOption(proxy.id)"
|
||||
:class="[
|
||||
'select-option',
|
||||
modelValue === proxy.id && 'select-option-selected'
|
||||
]"
|
||||
:class="['select-option', modelValue === proxy.id && 'select-option-selected']"
|
||||
>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="truncate font-medium">{{ proxy.name }}</span>
|
||||
<!-- Account count badge -->
|
||||
<span
|
||||
v-if="proxy.account_count !== undefined"
|
||||
class="flex-shrink-0 inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-gray-100 dark:bg-dark-600 text-gray-600 dark:text-gray-400"
|
||||
class="inline-flex flex-shrink-0 items-center rounded bg-gray-100 px-1.5 py-0.5 text-xs text-gray-600 dark:bg-dark-600 dark:text-gray-400"
|
||||
>
|
||||
{{ proxy.account_count }}
|
||||
</span>
|
||||
@@ -111,20 +134,24 @@
|
||||
<template v-if="testResults[proxy.id]">
|
||||
<span
|
||||
v-if="testResults[proxy.id].success"
|
||||
class="flex-shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-400"
|
||||
class="inline-flex flex-shrink-0 items-center gap-1 rounded bg-emerald-100 px-1.5 py-0.5 text-xs text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
|
||||
>
|
||||
<span v-if="testResults[proxy.id].country">{{ testResults[proxy.id].country }}</span>
|
||||
<span v-if="testResults[proxy.id].latency_ms">{{ testResults[proxy.id].latency_ms }}ms</span>
|
||||
<span v-if="testResults[proxy.id].country">{{
|
||||
testResults[proxy.id].country
|
||||
}}</span>
|
||||
<span v-if="testResults[proxy.id].latency_ms"
|
||||
>{{ testResults[proxy.id].latency_ms }}ms</span
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="flex-shrink-0 inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400"
|
||||
class="inline-flex flex-shrink-0 items-center rounded bg-red-100 px-1.5 py-0.5 text-xs text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
||||
>
|
||||
{{ t('admin.proxies.testFailed') }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 truncate">
|
||||
<div class="truncate text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ proxy.protocol }}://{{ proxy.host }}:{{ proxy.port }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,18 +164,45 @@
|
||||
class="test-btn"
|
||||
:title="t('admin.proxies.testConnection')"
|
||||
>
|
||||
<svg v-if="testingProxyIds.has(proxy.id)" class="w-3.5 h-3.5 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
<svg
|
||||
v-if="testingProxyIds.has(proxy.id)"
|
||||
class="h-3.5 w-3.5 animate-spin"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<svg v-else class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 010 1.972l-11.54 6.347a1.125 1.125 0 01-1.667-.986V5.653z" />
|
||||
<svg
|
||||
v-else
|
||||
class="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 010 1.972l-11.54 6.347a1.125 1.125 0 01-1.667-.986V5.653z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<svg
|
||||
v-if="modelValue === proxy.id"
|
||||
class="w-4 h-4 text-primary-500 flex-shrink-0"
|
||||
class="h-4 w-4 flex-shrink-0 text-primary-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -193,7 +247,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
disabled: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -212,7 +266,7 @@ const batchTesting = ref(false)
|
||||
|
||||
const selectedProxy = computed(() => {
|
||||
if (props.modelValue === null) return null
|
||||
return props.proxies.find(p => p.id === props.modelValue) || null
|
||||
return props.proxies.find((p) => p.id === props.modelValue) || null
|
||||
})
|
||||
|
||||
const selectedLabel = computed(() => {
|
||||
@@ -228,7 +282,7 @@ const filteredProxies = computed(() => {
|
||||
return props.proxies
|
||||
}
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
return props.proxies.filter(proxy => {
|
||||
return props.proxies.filter((proxy) => {
|
||||
const name = proxy.name.toLowerCase()
|
||||
const host = proxy.host.toLowerCase()
|
||||
return name.includes(query) || host.includes(query)
|
||||
@@ -320,27 +374,27 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.select-trigger {
|
||||
@apply w-full flex items-center justify-between gap-2;
|
||||
@apply px-4 py-2.5 rounded-xl text-sm;
|
||||
@apply flex w-full items-center justify-between gap-2;
|
||||
@apply rounded-xl px-4 py-2.5 text-sm;
|
||||
@apply bg-white dark:bg-dark-800;
|
||||
@apply border border-gray-200 dark:border-dark-600;
|
||||
@apply text-gray-900 dark:text-gray-100;
|
||||
@apply transition-all duration-200;
|
||||
@apply focus:outline-none focus:ring-2 focus:ring-primary-500/30 focus:border-primary-500;
|
||||
@apply focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/30;
|
||||
@apply hover:border-gray-300 dark:hover:border-dark-500;
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
.select-trigger-open {
|
||||
@apply ring-2 ring-primary-500/30 border-primary-500;
|
||||
@apply border-primary-500 ring-2 ring-primary-500/30;
|
||||
}
|
||||
|
||||
.select-trigger-disabled {
|
||||
@apply bg-gray-100 dark:bg-dark-900 cursor-not-allowed opacity-60;
|
||||
@apply cursor-not-allowed bg-gray-100 opacity-60 dark:bg-dark-900;
|
||||
}
|
||||
|
||||
.select-value {
|
||||
@apply flex-1 text-left truncate;
|
||||
@apply flex-1 truncate text-left;
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
@@ -348,7 +402,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.select-dropdown {
|
||||
@apply absolute z-[100] w-full mt-2;
|
||||
@apply absolute z-[100] mt-2 w-full;
|
||||
@apply bg-white dark:bg-dark-800;
|
||||
@apply rounded-xl;
|
||||
@apply border border-gray-200 dark:border-dark-700;
|
||||
@@ -362,7 +416,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.select-search {
|
||||
@apply flex-1 flex items-center gap-2;
|
||||
@apply flex flex-1 items-center gap-2;
|
||||
}
|
||||
|
||||
.select-search-input {
|
||||
@@ -373,10 +427,10 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.batch-test-btn {
|
||||
@apply flex-shrink-0 p-1.5 rounded-lg;
|
||||
@apply flex-shrink-0 rounded-lg p-1.5;
|
||||
@apply text-gray-500 hover:text-emerald-600 dark:hover:text-emerald-400;
|
||||
@apply hover:bg-emerald-50 dark:hover:bg-emerald-900/20;
|
||||
@apply transition-colors disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
@apply transition-colors disabled:cursor-not-allowed disabled:opacity-50;
|
||||
}
|
||||
|
||||
.select-options {
|
||||
@@ -406,10 +460,10 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
@apply flex-shrink-0 p-1 rounded;
|
||||
@apply flex-shrink-0 rounded p-1;
|
||||
@apply text-gray-400 hover:text-emerald-600 dark:hover:text-emerald-400;
|
||||
@apply hover:bg-emerald-50 dark:hover:bg-emerald-900/20;
|
||||
@apply transition-colors disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
@apply transition-colors disabled:cursor-not-allowed disabled:opacity-50;
|
||||
}
|
||||
|
||||
/* Dropdown animation */
|
||||
|
||||
Reference in New Issue
Block a user