refactorign
Main daploy / deploy (push) Successful in 47s

This commit is contained in:
2024-11-19 21:26:29 +02:00
parent 66093e9c80
commit 663f6ac1ab
7 changed files with 152 additions and 96 deletions
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 103 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path fill="#FB2500" stroke="None" d="M94 384c-10.69 10.9-19.59 23.73-29.75 35.25C57.4 429.31 33.45 446.87 54 455h65c61.67-2.91 126.65 7.03 185.33-7.67 117.65-27.97 185.84-172.81 132.59-281.25C390.54 54.32 238.47 9.09 139.7 77.7 34.91 139.96 15.23 294.73 94 384m236-226v177c-26.16-6.68-51.76-15.48-78-22 8.51-24.46 20.5-47.86 29.98-72.02 5.09-16.89-18.53-19.16-25.73-6.73-18.43 15.59-37.17 30.71-55.5 46.5-9.63 13.08-22.66.79-34.43-2.07-12.21-5.3-25.45-9.3-37.32-14.68 66.54-35.63 133.64-72.07 201-106Z"/></svg>

After

Width:  |  Height:  |  Size: 573 B

+53
View File
@@ -0,0 +1,53 @@
import { calculateFontSizeForText } from '@/utils/calculateFontSizeForText'
/**
* Добавление текста в SVG, прибитого к нижнему краю.
* @param svgText Исходный SVG в виде строки.
* @param label Надпись для добавления.
* @param color Цвет текста.
* @param marginMultiplier Множитель относительного отступа.
* @returns SVG с добавленным текстом.
*/
export function addLabelToSvg(
svgText: string,
label: string,
color: string,
marginMultiplier: number = 0.1,
): string {
if (!label) {
return svgText
}
const parser = new DOMParser()
const svgDoc = parser.parseFromString(svgText, 'image/svg+xml')
const svgElement = svgDoc.querySelector('svg')
if (svgElement) {
// Увеличиваем viewBox для текста
const viewBox = svgElement.getAttribute('viewBox')?.split(' ').map(Number)
if (viewBox) {
const fontSize = calculateFontSizeForText(label, viewBox[2])
const textMargin = Math.min(fontSize, viewBox[3] * marginMultiplier)
viewBox[3] += fontSize + textMargin // Увеличиваем высоту
svgElement.setAttribute('viewBox', viewBox.join(' '))
// Добавляем текст
const textElement = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'text')
textElement.setAttribute('x', '50%')
textElement.setAttribute('y', '100%') // Устанавливаем Y на 100% высоты
textElement.setAttribute('text-anchor', 'middle')
textElement.setAttribute('alignment-baseline', 'baseline') // Привязываем к нижнему краю
textElement.setAttribute('dominant-baseline', 'text-after-edge') // Альтернативный способ
textElement.setAttribute('fill', color)
textElement.setAttribute('font-size', fontSize.toString())
textElement.setAttribute('font-family', 'Arial, sans-serif')
textElement.textContent = label
svgElement.appendChild(textElement)
}
}
const serializer = new XMLSerializer()
return serializer.serializeToString(svgDoc)
}
+25
View File
@@ -0,0 +1,25 @@
import { optimize } from 'svgo'
/**
* Оптимизация SVG с использованием SVGO.
* @param svgText string изображения SVG.
* @returns Строка оптимизированного SVG.
*/
export function optimizeSvg(svgText: string): string {
const optimizedSvg = optimize(svgText, {
plugins: [
{ name: 'removeDoctype' },
{ name: 'removeComments' },
{ name: 'removeDimensions' },
{ name: 'convertShapeToPath' },
{ name: 'convertPathData' },
{ name: 'mergePaths' },
],
})
if (!optimizedSvg.data) {
throw new Error('Ошибка оптимизации SVG')
}
return optimizedSvg.data
}
+20
View File
@@ -0,0 +1,20 @@
/**
* Заменяет атрибут `fill` в SVG-коде.
* @param svgText Исходный SVG в виде строки.
* @param fillColor Новый цвет для fill.
* @returns Измененный SVG-код.
*/
export function replaceFillInSvg(svgText: string, fillColor: string): string {
const parser = new DOMParser()
const svgDoc = parser.parseFromString(svgText, 'image/svg+xml')
const paths = svgDoc.querySelectorAll('path, rect, circle, polygon, ellipse')
paths.forEach((path) => {
if (path.hasAttribute('fill')) {
path.setAttribute('fill', fillColor)
}
})
const serializer = new XMLSerializer()
return serializer.serializeToString(svgDoc)
}
+53 -95
View File
@@ -9,6 +9,9 @@
<div v-if="!svgQueryIsLoading && svgQueryData" class="border p-4 bg-white rounded-lg mb-6"> <div v-if="!svgQueryIsLoading && svgQueryData" class="border p-4 bg-white rounded-lg mb-6">
<img :src="svgQueryData" alt="QR‑код" /> <img :src="svgQueryData" alt="QR‑код" />
</div> </div>
<p v-else-if="svgQueryIsError" class="text-red-500 mt-4">
Ошибка генерации QRкода: {{ svgQueryError?.message }}
</p>
<!-- Сообщение об ошибке --> <!-- Сообщение об ошибке -->
<p v-if="qrCodeQueryIsError" class="text-red-500 mt-4"> <p v-if="qrCodeQueryIsError" class="text-red-500 mt-4">
@@ -63,14 +66,17 @@
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useQuery } from '@tanstack/vue-query' import { useQuery } from '@tanstack/vue-query'
import QRCodeStyling from 'qr-code-styling' import QRCodeStyling from 'qr-code-styling'
import { optimize } from 'svgo' import { optimizeSvg } from '@/utils/images/svg/optimizeSvg'
import { replaceFillInSvg } from '@/utils/images/svg/replaceFillInSvg'
import { getQrCodeDocument } from '@/api/qrCode' import { getQrCodeDocument } from '@/api/qrCode'
import { type QRCodeDocument } from '@/types/DBDocumentTypes' import { type QRCodeDocument } from '@/types/DBDocumentTypes'
import { calculateFontSizeForText } from '@/utils/images/calculateFontSizeForText'
import { downloadImage } from '@/utils/images/downloadImage' import { downloadImage } from '@/utils/images/downloadImage'
import { addLabelToSvg } from '@/utils/images/svg/addLabelToSvg'
import logo from '@/assets/logo.svg?raw'
const { qr_code_uri } = defineProps<{ qr_code_uri: QRCodeDocument['uri'] }>() const { qr_code_uri } = defineProps<{ qr_code_uri: QRCodeDocument['uri'] }>()
const color = ref('#f43f5e') const color = ref('#000') // Цвет для текста QR-кода
const logoColor = ref(color.value) // Цвет логотипа // #FB2500
const label = ref('Сообщите о проблеме или задайте вопрос') const label = ref('Сообщите о проблеме или задайте вопрос')
const width = ref(256 * 4) const width = ref(256 * 4)
const height = ref(width.value) const height = ref(width.value)
@@ -85,123 +91,75 @@ const {
queryFn: () => getQrCodeDocument(qr_code_uri), queryFn: () => getQrCodeDocument(qr_code_uri),
}) })
// Инициализация QRCodeStyling
const qrCode = new QRCodeStyling({
width: width.value,
height: height.value,
data: '',
image: '/images/logo.svg',
imageOptions: {
margin: 20,
// hideBackgroundDots: false,
imageSize: 0.45,
},
type: 'svg',
backgroundOptions: {
color: 'transparent',
},
cornersSquareOptions: {
type: 'extra-rounded',
},
cornersDotOptions: {
type: 'dot',
},
dotsOptions: {
color: color.value,
type: 'classy-rounded',
},
})
// Получение имени файла // Получение имени файла
const sanitizedFileName = computed(() => { const sanitizedFileName = computed(() => {
const sanitized = qrCodeDoc.value?.name const sanitized = qrCodeDoc.value?.name
?.replace(/[<>:"/\\|?*\x00-\x1F]/g, '') // Убираем запрещённые символы ?.replace(/[<>:"/\\|?*\x00-\x1F]/g, '') // Убираем запрещённые символы
.replace(/\s+/g, '_') // Заменяем пробелы на подчёркивания .replace(/\s+/g, ' ') // Заменяем пробелы на подчёркивания
.trim() .trim()
return sanitized ? `${sanitized}_QR-Code_HereConnect` : 'QR-Code_HereConnect' return sanitized ? `${sanitized} QR-Code - НаСвязи` : 'QR-Code - НаСвязи'
}) })
// Хук для генерации и оптимизации SVG // Генерация и оптимизация SVG
const { const {
isLoading: svgQueryIsLoading, isLoading: svgQueryIsLoading,
isError: svgQueryIsError,
data: svgQueryData, data: svgQueryData,
error: svgQueryError, error: svgQueryError,
} = useQuery({ } = useQuery({
queryKey: ['svg', computed(() => qr_code_uri)], queryKey: ['svg', computed(() => qr_code_uri)],
enabled: computed(() => !!qrCodeDoc.value?.url), enabled: computed(() => !!qrCodeDoc.value?.url),
retry: false,
queryFn: async () => { queryFn: async () => {
qrCode.update({ data: qrCodeDoc.value!.url }) // Заменяем цвет `fill` в логотипе
const updatedLogo = replaceFillInSvg(logo, logoColor.value)
// Добавляем текст в логотип (если нужно)
const logoWithText = addLabelToSvg(updatedLogo, 'НаСвязи', logoColor.value, 0.01)
const image = URL.createObjectURL(new Blob([logoWithText], { type: 'image/svg+xml' }))
// Инициализация QRCodeStyling
const qrCode = new QRCodeStyling({
width: width.value,
height: height.value,
data: qrCodeDoc.value!.url,
image,
imageOptions: {
margin: 20,
imageSize: 0.45,
},
type: 'svg',
backgroundOptions: {
color: 'transparent',
},
cornersSquareOptions: {
type: 'extra-rounded',
},
cornersDotOptions: {
type: 'dot',
},
dotsOptions: {
color: color.value,
type: 'classy-rounded',
},
})
const blob = await qrCode.getRawData('svg') const blob = await qrCode.getRawData('svg')
if (blob && blob instanceof Blob) { if (blob && blob instanceof Blob) {
const text = await blob.text() const text = await blob.text()
let resultSvg = optimizeSvg(text)
const optimizedSvg = optimize(text, { if (!resultSvg) {
plugins: [
{ name: 'removeDoctype' },
{ name: 'removeComments' },
{ name: 'removeDimensions' },
{ name: 'convertShapeToPath' },
{ name: 'convertPathData' },
{ name: 'mergePaths' },
],
})
if (optimizedSvg.data) {
if (!label.value) {
// Если label пуст, возвращаем SVG без изменений
return URL.createObjectURL(new Blob([optimizedSvg.data], { type: 'image/svg+xml' }))
}
// Добавляем надпись и увеличиваем viewBox
const parser = new DOMParser()
const svgDoc = parser.parseFromString(optimizedSvg.data, 'image/svg+xml')
const svgElement = svgDoc.querySelector('svg')
if (svgElement) {
// Увеличиваем viewBox
const viewBox = svgElement.getAttribute('viewBox')?.split(' ').map(Number)
if (viewBox) {
// Расчет реального размера шрифта
const fontSize = calculateFontSizeForText(label.value, viewBox[2])
// Рассчитываем вертикальное расстояние между QR-кодом и текстом
const textMargin = Math.min(fontSize, viewBox[3] * 0.1) // Максимум 10% от высоты QR-кода
viewBox[3] += fontSize + textMargin // Увеличиваем высоту для текста
svgElement.setAttribute('viewBox', viewBox.join(' '))
// Добавляем текст
const textElement = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'text')
textElement.setAttribute('x', '50%')
textElement.setAttribute('y', viewBox[3] - fontSize / 2) // Уменьшаем нижний отступ
textElement.setAttribute('text-anchor', 'middle')
textElement.setAttribute('fill', color.value)
textElement.setAttribute('font-size', fontSize.toString())
textElement.setAttribute('font-family', 'Arial, sans-serif')
textElement.textContent = label.value
svgElement.appendChild(textElement)
}
}
// Сериализуем обратно в строку
const serializer = new XMLSerializer()
const updatedSvg = serializer.serializeToString(svgDoc)
return URL.createObjectURL(new Blob([updatedSvg], { type: 'image/svg+xml' }))
} else {
throw new Error('Ошибка оптимизации SVG') throw new Error('Ошибка оптимизации SVG')
} }
if (label.value) {
resultSvg = addLabelToSvg(resultSvg, label.value, color.value)
}
return URL.createObjectURL(new Blob([resultSvg], { type: 'image/svg+xml' }))
} else { } else {
throw new Error('Ошибка генерации QR‑кода') throw new Error('Ошибка генерации QR‑кода')
} }
}, },
onSettled(data) {
if (!data && svgQueryError) {
console.error('Ошибка загрузки SVG:', svgQueryError.message)
}
},
}) })
// Скачивание PNG // Скачивание PNG