update flags flagcdn -> jsdelivr (unavaluable from Russia)

This commit is contained in:
austnv
2026-06-15 00:55:19 +03:00
parent c8486ca001
commit bf9b095b27
3 changed files with 625 additions and 584 deletions
+577 -490
View File
File diff suppressed because it is too large Load Diff
+38 -82
View File
@@ -3,7 +3,7 @@
<div class="container mx-auto px-6 py-2 md:px-12"> <div class="container mx-auto px-6 py-2 md:px-12">
<div class="flex items-center justify-between gap-3 flex-wrap"> <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-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400 text-xs">🌐 Ваш IP:</span> <span class="text-gray-500 dark:text-gray-400 text-xs">🌐 Ваш IP:</span>
@@ -11,18 +11,10 @@
{{ ipData.ip }} {{ ipData.ip }}
</code> </code>
</div> </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="relative flex h-1.5 w-1.5">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75" <span class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75" :class="isProtected ? 'bg-green-500' : 'bg-amber-500'"></span>
: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="relative inline-flex rounded-full h-1.5 w-1.5"
:class="isProtected ? 'bg-green-600' : 'bg-amber-600'"></span>
</span> </span>
{{ isProtected ? 'Защищено' : 'Не защищено' }} {{ isProtected ? 'Защищено' : 'Не защищено' }}
</span> </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 items-center justify-center gap-1 text-xs text-gray-500 dark:text-gray-400">
<div class="flex h-4 items-center"> <!-- Локальный SVG-флаг -->
<img <img
v-if="flagUrl" v-if="countryCode"
:src="flagUrl" :src="`https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/flags/4x3/${countryCode}.svg`"
:alt="countryCode" :alt="countryCode"
class="w-4 h-3 object-cover" class="h-3 w-auto"
@error="handleFlagError"
/> />
<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) }}
</span>
</template>
</div> </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) }}
</span>
</template>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -83,67 +74,47 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { getAllCountries, withFlags } from '@tw-labs/countries' import { getAllCountries } from '@tw-labs/countries'
const ipData = ref(null) const ipData = ref(null)
const loading = ref(true) const loading = ref(true)
const error = ref(false) const error = ref(false)
const flagError = ref(false)
// Загружаем список стран один раз // Список стран для маппинга названия -> код
const countriesList = getAllCountries() const countriesList = getAllCountries()
// Функция поиска страны по названию
function findCountryByName(countryName) { function findCountryByName(countryName) {
if (!countryName) return null 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 if (found) return found
// частичное совпадение (например "Russian Federation" vs "Russia")
// Частичное совпадение (для "Russian Federation" vs "Russia" и т.п.) const partial = countriesList.find(c =>
const partial = countriesList.find( countryName.toLowerCase().includes(c.label.toLowerCase()) ||
c => countryName.toLowerCase().includes(c.label.toLowerCase()) || c.label.toLowerCase().includes(countryName.toLowerCase())
c.label.toLowerCase().includes(countryName.toLowerCase())
) )
return partial || null return partial || null
} }
async function fetchIp() { async function fetchIp() {
loading.value = true loading.value = true
error.value = false error.value = false
flagError.value = false
try { try {
const response = await fetch('/api/v1/ip') const response = await fetch('/api/v1/ip')
if (!response.ok) throw new Error(`HTTP ${response.status}`) if (!response.ok) throw new Error(`HTTP ${response.status}`)
const data = await response.json() const data = await response.json()
// Находим код страны по названию
const countryName = data.location?.country const countryName = data.location?.country
const foundCountry = findCountryByName(countryName) const foundCountry = findCountryByName(countryName)
const countryCode = foundCountry?.code || null const countryCode = foundCountry?.code || null
// Добавляем флаговые данные через withFlags
let countryWithFlags = null
if (foundCountry) {
countryWithFlags = withFlags(foundCountry)
}
ipData.value = { ipData.value = {
...data, ...data,
location: { location: {
...data.location, ...data.location,
computed_country_code: countryCode computed_country_code: countryCode
}, }
_flags: countryWithFlags
} }
} catch (err) { } catch (err) {
console.error('IP fetch error:', err) console.error('IP fetch error:', err)
error.value = true error.value = true
@@ -155,32 +126,17 @@ async function fetchIp() {
const isProtected = computed(() => { const isProtected = computed(() => {
if (!ipData.value) return false 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(() => { 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) { function truncateText(text, maxLength) {
if (!text) return '' if (!text) return ''
return text.length > maxLength ? text.slice(0, maxLength) + '...' : text return text.length > maxLength ? text.slice(0, maxLength) + '...' : text
+5 -7
View File
@@ -5,7 +5,6 @@ import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import { default as Sitemap } from 'vite-plugin-sitemap'; import { default as Sitemap } from 'vite-plugin-sitemap';
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
@@ -13,12 +12,8 @@ export default defineConfig({
tailwindcss(), tailwindcss(),
Sitemap({ Sitemap({
hostname: 'https://uvpn.shop', hostname: 'https://uvpn.shop',
// Плагин сам прочитает маршруты из вашего router, но если нужно указать вручную: dynamicRoutes: ['/docs/policy', '/docs/agreement'],
// routes: ['/', '/docs'], // можно опустить — он определит автоматически outDir: 'dist',
// Если у вас динамические маршруты (например, /docs/:docName), их нужно добавить отдельно:
dynamicRoutes: ['/docs/policy', '/docs/agreement'], // сюда перечислите все возможные динамические URL
// Настройки changefreq, priority и т.д.:
outDir: 'dist', // папка сборки
changefreq: 'weekly', changefreq: 'weekly',
priority: 0.8 priority: 0.8
}) })
@@ -28,4 +23,7 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url)) '@': fileURLToPath(new URL('./src', import.meta.url))
}, },
}, },
build: {
chunkSizeWarningLimit: 1000
}
}) })