update flags flagcdn -> jsdelivr (unavaluable from Russia)
This commit is contained in:
Generated
+577
-490
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,8 @@
|
||||
<div v-if="ipData" class="bg-gray-50/80 dark:bg-gray-800/30 border-b border-gray-200 dark:border-gray-700 text-sm backdrop-blur-sm">
|
||||
<div class="container mx-auto px-6 py-2 md:px-12">
|
||||
<div class="flex items-center justify-between gap-3 flex-wrap">
|
||||
|
||||
<!-- IP и статус защиты -->
|
||||
|
||||
<!-- Блок с IP и защитой -->
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">🌐 Ваш IP:</span>
|
||||
@@ -11,18 +11,10 @@
|
||||
{{ ipData.ip }}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="inline-flex items-center gap-2 text-xs px-2 py-0.5 rounded-full font-medium"
|
||||
:class="isProtected
|
||||
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||
: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'"
|
||||
>
|
||||
<span class="inline-flex items-center gap-2 text-xs px-2 py-0.5 rounded-full font-medium" :class="isProtected ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' : 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'">
|
||||
<span class="relative flex h-1.5 w-1.5">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75"
|
||||
:class="isProtected ? 'bg-green-500' : 'bg-amber-500'"></span>
|
||||
<span class="relative inline-flex rounded-full h-1.5 w-1.5"
|
||||
:class="isProtected ? 'bg-green-600' : 'bg-amber-600'"></span>
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75" :class="isProtected ? 'bg-green-500' : 'bg-amber-500'"></span>
|
||||
<span class="relative inline-flex rounded-full h-1.5 w-1.5" :class="isProtected ? 'bg-green-600' : 'bg-amber-600'"></span>
|
||||
</span>
|
||||
{{ isProtected ? 'Защищено' : 'Не защищено' }}
|
||||
</span>
|
||||
@@ -30,32 +22,31 @@
|
||||
|
||||
<!-- Локация с флагом -->
|
||||
<div class="flex items-center justify-center gap-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
<div class="flex h-4 items-center">
|
||||
<!-- Локальный SVG-флаг -->
|
||||
<img
|
||||
v-if="flagUrl"
|
||||
:src="flagUrl"
|
||||
v-if="countryCode"
|
||||
:src="`https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/flags/4x3/${countryCode}.svg`"
|
||||
:alt="countryCode"
|
||||
class="w-4 h-3 object-cover"
|
||||
@error="handleFlagError"
|
||||
class="h-3 w-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template v-if="ipData.location?.city">
|
||||
<span class="hidden sm:inline">{{ ipData.location.city }}</span>
|
||||
<span class="text-gray-400 hidden sm:inline">•</span>
|
||||
</template>
|
||||
|
||||
<span v-if="ipData.location?.country" class="text-gray-600 dark:text-gray-300 font-medium hidden sm:inline">
|
||||
{{ ipData.location.country }}
|
||||
</span>
|
||||
|
||||
<template v-if="ipData.company?.name">
|
||||
<span class="text-gray-400 hidden md:inline">•</span>
|
||||
<span class="hidden md:inline text-gray-400">
|
||||
{{ truncateText(ipData.company.name, 30) }}
|
||||
|
||||
<template v-if="ipData.location?.city">
|
||||
<span class="hidden sm:inline">{{ ipData.location.city }}</span>
|
||||
<span class="text-gray-400 hidden sm:inline">•</span>
|
||||
</template>
|
||||
|
||||
<span v-if="ipData.location?.country" class="text-gray-600 dark:text-gray-300 font-medium hidden sm:inline">
|
||||
{{ ipData.location.country }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template v-if="ipData.company?.name">
|
||||
<span class="text-gray-400 hidden md:inline">•</span>
|
||||
<span class="hidden md:inline text-gray-400">
|
||||
{{ truncateText(ipData.company.name, 30) }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,67 +74,47 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getAllCountries, withFlags } from '@tw-labs/countries'
|
||||
import { getAllCountries } from '@tw-labs/countries'
|
||||
|
||||
const ipData = ref(null)
|
||||
const loading = ref(true)
|
||||
const error = ref(false)
|
||||
const flagError = ref(false)
|
||||
|
||||
// Загружаем список стран один раз
|
||||
// Список стран для маппинга названия -> код
|
||||
const countriesList = getAllCountries()
|
||||
|
||||
// Функция поиска страны по названию
|
||||
function findCountryByName(countryName) {
|
||||
if (!countryName) return null
|
||||
|
||||
// Точное совпадение
|
||||
const found = countriesList.find(
|
||||
c => c.label.toLowerCase() === countryName.toLowerCase()
|
||||
)
|
||||
|
||||
const found = countriesList.find(c => c.label.toLowerCase() === countryName.toLowerCase())
|
||||
if (found) return found
|
||||
|
||||
// Частичное совпадение (для "Russian Federation" vs "Russia" и т.п.)
|
||||
const partial = countriesList.find(
|
||||
c => countryName.toLowerCase().includes(c.label.toLowerCase()) ||
|
||||
c.label.toLowerCase().includes(countryName.toLowerCase())
|
||||
// частичное совпадение (например "Russian Federation" vs "Russia")
|
||||
const partial = countriesList.find(c =>
|
||||
countryName.toLowerCase().includes(c.label.toLowerCase()) ||
|
||||
c.label.toLowerCase().includes(countryName.toLowerCase())
|
||||
)
|
||||
|
||||
return partial || null
|
||||
}
|
||||
|
||||
async function fetchIp() {
|
||||
loading.value = true
|
||||
error.value = false
|
||||
flagError.value = false
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/v1/ip')
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
||||
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// Находим код страны по названию
|
||||
const countryName = data.location?.country
|
||||
const foundCountry = findCountryByName(countryName)
|
||||
const countryCode = foundCountry?.code || null
|
||||
|
||||
// Добавляем флаговые данные через withFlags
|
||||
let countryWithFlags = null
|
||||
if (foundCountry) {
|
||||
countryWithFlags = withFlags(foundCountry)
|
||||
}
|
||||
|
||||
|
||||
ipData.value = {
|
||||
...data,
|
||||
location: {
|
||||
...data.location,
|
||||
computed_country_code: countryCode
|
||||
},
|
||||
_flags: countryWithFlags
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('IP fetch error:', err)
|
||||
error.value = true
|
||||
@@ -155,32 +126,17 @@ async function fetchIp() {
|
||||
|
||||
const isProtected = computed(() => {
|
||||
if (!ipData.value) return false
|
||||
return ipData.value.security?.vpn || ipData.value.security?.proxy || ipData.value.security?.tor || ipData.value.security?.hosting
|
||||
return ipData.value.security?.vpn ||
|
||||
ipData.value.security?.proxy ||
|
||||
ipData.value.security?.tor ||
|
||||
ipData.value.security?.hosting
|
||||
})
|
||||
|
||||
// Код страны (вычисленный)
|
||||
const countryCode = computed(() => {
|
||||
return ipData.value?.location?.computed_country_code || null
|
||||
const code = ipData.value?.location?.computed_country_code
|
||||
return code ? code.toLowerCase() : null
|
||||
})
|
||||
|
||||
// URL флага
|
||||
const flagUrl = computed(() => {
|
||||
// Сначала пробуем взять SVG из withFlags
|
||||
if (ipData.value?._flags?.flagSvg && !flagError.value) {
|
||||
return ipData.value._flags.flagSvg
|
||||
}
|
||||
|
||||
// Если нет — формируем сами через flagcdn
|
||||
const code = countryCode.value
|
||||
if (!code || flagError.value) return null
|
||||
|
||||
return `https://flagcdn.com/${code.toLowerCase()}.svg`
|
||||
})
|
||||
|
||||
function handleFlagError() {
|
||||
flagError.value = true
|
||||
}
|
||||
|
||||
function truncateText(text, maxLength) {
|
||||
if (!text) return ''
|
||||
return text.length > maxLength ? text.slice(0, maxLength) + '...' : text
|
||||
|
||||
+5
-7
@@ -5,7 +5,6 @@ import vue from '@vitejs/plugin-vue'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { default as Sitemap } from 'vite-plugin-sitemap';
|
||||
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -13,12 +12,8 @@ export default defineConfig({
|
||||
tailwindcss(),
|
||||
Sitemap({
|
||||
hostname: 'https://uvpn.shop',
|
||||
// Плагин сам прочитает маршруты из вашего router, но если нужно указать вручную:
|
||||
// routes: ['/', '/docs'], // можно опустить — он определит автоматически
|
||||
// Если у вас динамические маршруты (например, /docs/:docName), их нужно добавить отдельно:
|
||||
dynamicRoutes: ['/docs/policy', '/docs/agreement'], // сюда перечислите все возможные динамические URL
|
||||
// Настройки changefreq, priority и т.д.:
|
||||
outDir: 'dist', // папка сборки
|
||||
dynamicRoutes: ['/docs/policy', '/docs/agreement'],
|
||||
outDir: 'dist',
|
||||
changefreq: 'weekly',
|
||||
priority: 0.8
|
||||
})
|
||||
@@ -28,4 +23,7 @@ export default defineConfig({
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 1000
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user