Переписал на vue3
@@ -1,2 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.DS_Store
|
||||||
|
*.local
|
||||||
__pycache__
|
__pycache__
|
||||||
ideas.md
|
ideas.md
|
||||||
|
|||||||
@@ -1,9 +1,40 @@
|
|||||||
## [Сайт](https://iliamiheev.github.io/iQR/) на котором можно генерировать QR коды
|
## [Сайт](https://iliamiheev.github.io/iQR/) на котором можно генерировать QR коды
|
||||||
|
|
||||||
Вводишь текст и генерируешь. Всё просто
|
Вводишь текст и генерируешь. Всё просто.
|
||||||
|
|
||||||
|
Проект переписан на **Vue 3 + Vite**.
|
||||||
|
|
||||||
|
### Запуск локально
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Приложение откроется по адресу `http://localhost:5173/iQR/`.
|
||||||
|
|
||||||
|
### Сборка для GitHub Pages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Содержимое папки `dist/` нужно задеплоить на GitHub Pages (корень сайта — `dist`, base path уже настроен как `/iQR/`).
|
||||||
|
|
||||||
|
### Структура
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
App.vue — главный компонент
|
||||||
|
composables/
|
||||||
|
useTheme.js — тёмная/светлая тема
|
||||||
|
useQrCode.js — генерация, скачивание, масштаб
|
||||||
|
assets/style.css — стили
|
||||||
|
public/img/ — иконки, курсоры, favicon
|
||||||
|
```
|
||||||
|
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="ru" class="dark">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" href="./img/favicon.ico" type="image/x-icon">
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/img/favicon.ico" type="image/x-icon">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>iQR — генератор QR кодов</title>
|
<title>iQR — генератор QR кодов</title>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/qrcode/build/qrcode.min.js"></script>
|
|
||||||
<link rel="stylesheet" href="./style.css">
|
|
||||||
|
|
||||||
<!-- Meta Tags -->
|
|
||||||
<meta name="title" content="Генератор QR кодов">
|
<meta name="title" content="Генератор QR кодов">
|
||||||
<meta name="description" content="Простой в использовании генератор QR кодов. Сделано программистом с душой">
|
<meta name="description" content="Простой в использовании генератор QR кодов. Сделано программистом с душой">
|
||||||
<meta name="keywords" content="QR, QR код, iQR">
|
<meta name="keywords" content="QR, QR код, iQR">
|
||||||
@@ -17,62 +13,25 @@
|
|||||||
<meta name="robots" content="all">
|
<meta name="robots" content="all">
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
<meta name="revisit-after" content="8 days">
|
<meta name="revisit-after" content="8 days">
|
||||||
<meta name="description" content="Russian">
|
|
||||||
<meta name="author" content="Ilia Miheev">
|
<meta name="author" content="Ilia Miheev">
|
||||||
<meta name="theme-color" content="orangered">
|
<meta name="theme-color" content="orangered">
|
||||||
|
|
||||||
<!-- Open Graph Tags -->
|
|
||||||
<meta property="og:title" content="Генератор QR кодов">
|
<meta property="og:title" content="Генератор QR кодов">
|
||||||
<meta property="og:description" content="Простой в использовании генератор QR кодов. Сделано программистом с любовью к пользователям">
|
<meta property="og:description" content="Простой в использовании генератор QR кодов. Сделано программистом с любовью к пользователям">
|
||||||
<meta property="og:image" content="https://avatars.githubusercontent.com/u/135439814?v=4">
|
<meta property="og:image" content="https://avatars.githubusercontent.com/u/135439814?v=4">
|
||||||
<meta property="og:url" content="https://iliamiheev.github.io/iQR/">
|
<meta property="og:url" content="https://iliamiheev.github.io/iQR/">
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
|
|
||||||
<!-- Yandex.Metrika counter -->
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
(function (m, e, t, r, i, k, a) { m[i] = m[i] || function () { (m[i].a = m[i].a || []).push(arguments) }; m[i].l = 1 * new Date(); for (var j = 0; j < document.scripts.length; j++) { if (document.scripts[j].src === r) { return; } } k = e.createElement(t), a = e.getElementsByTagName(t)[0], k.async = 1, k.src = r, a.parentNode.insertBefore(k, a) })(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=103902084', 'ym'); ym(103902084, 'init', { ssr: true, webvisor: true, clickmap: true, ecommerce: "dataLayer", accurateTrackBounce: true, trackLinks: true });
|
(function (m, e, t, r, i, k, a) { m[i] = m[i] || function () { (m[i].a = m[i].a || []).push(arguments) }; m[i].l = 1 * new Date(); for (var j = 0; j < document.scripts.length; j++) { if (document.scripts[j].src === r) { return; } } k = e.createElement(t), a = e.getElementsByTagName(t)[0], k.async = 1, k.src = r, a.parentNode.insertBefore(k, a) })(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=103902084', 'ym'); ym(103902084, 'init', { ssr: true, webvisor: true, clickmap: true, ecommerce: "dataLayer", accurateTrackBounce: true, trackLinks: true });
|
||||||
</script>
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<div><img src="https://mc.yandex.ru/watch/103902084" style="position:absolute; left:-9999px;" alt="" /></div>
|
<div><img src="https://mc.yandex.ru/watch/103902084" style="position:absolute; left:-9999px;" alt="" /></div>
|
||||||
</noscript>
|
</noscript>
|
||||||
<!-- /Yandex.Metrika counter -->
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<!-- Разработчик: Илья) -->
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
<body>
|
|
||||||
<div class="logo">
|
|
||||||
<h1 id="h1">Генератор QR-кодов</h1>
|
|
||||||
<a href="https://github.com/IliaMiheev/iQR" target="_blank" title="Github">
|
|
||||||
<img src="./img/fluidicon.png" alt="GitHub" style="width: 40px; height: 40px;">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inputs">
|
|
||||||
<textarea id="text-input" cols="30" rows="3" placeholder="Текст для генерации"></textarea>
|
|
||||||
<div id="isLightTheme" class="themeSwitch">
|
|
||||||
<div class="block" title="Изменить тему">
|
|
||||||
<input data-index="0" id="cheap-49" type="checkbox" />
|
|
||||||
<label for="cheap-49"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btns btns1">
|
|
||||||
<button class="btn" id="generate-btn" title="Создать QR-код">Создать</button>
|
|
||||||
<button class="btn" id="download-qr-btn" title="Скачать QR-код">Скачать</button>
|
|
||||||
<button class="btn3" id="del-qr-btn" title="Очистить поля">Очистить</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btns btns2">
|
|
||||||
<button title="Увеличить размер QR кода" id="zoom-in-btn">+</button>
|
|
||||||
<button title="Уменьшить размер QR кода" id="zoom-out-btn">-</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p id="error"></p>
|
|
||||||
<div id="qr-code"></div>
|
|
||||||
|
|
||||||
<script src="./script.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "iqr",
|
||||||
|
"private": true,
|
||||||
|
"version": "2.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"vue": "^3.5.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"vite": "^6.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
@@ -1,191 +0,0 @@
|
|||||||
const textInput = document.getElementById('text-input');
|
|
||||||
const generateBtn = document.getElementById('generate-btn');
|
|
||||||
const downloadQrBtn = document.getElementById('download-qr-btn');
|
|
||||||
const qrCodeDiv = document.getElementById('qr-code');
|
|
||||||
const delBtn = document.getElementById('del-qr-btn');
|
|
||||||
const textError = document.getElementById('error');
|
|
||||||
const zoomInBtn = document.getElementById('zoom-in-btn');
|
|
||||||
const zoomOutBtn = document.getElementById('zoom-out-btn');
|
|
||||||
const checkbox = document.getElementById('isLightTheme');
|
|
||||||
const html = document.getElementsByTagName('html')[0];
|
|
||||||
let scale = 1; // Начальный масштаб
|
|
||||||
|
|
||||||
function createTheme(nameTheme) {
|
|
||||||
if (nameTheme == 'dark') {
|
|
||||||
html.style.backgroundColor = '#1e1e1e';
|
|
||||||
changeButtonsColor('#fff')
|
|
||||||
} else if (nameTheme == 'light') {
|
|
||||||
html.style.backgroundColor = '#fff';
|
|
||||||
changeButtonsColor('#1e1e1e')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Установление значения поля ввода из local storage и темы сайта
|
|
||||||
window.onload = () => {
|
|
||||||
// Установка темы сайта
|
|
||||||
let oldTheme = localStorage.getItem('theme')
|
|
||||||
if (oldTheme){
|
|
||||||
createTheme(oldTheme)
|
|
||||||
|
|
||||||
if (oldTheme == 'light') {
|
|
||||||
checkbox.checked = 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
||||||
if (darkModeMediaQuery.matches) {
|
|
||||||
createTheme('dark')
|
|
||||||
} else {
|
|
||||||
createTheme('light')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Восстановление масштаба
|
|
||||||
let oldScale = localStorage.getItem('scale')
|
|
||||||
if (oldScale) {
|
|
||||||
scale = oldScale
|
|
||||||
}
|
|
||||||
|
|
||||||
// Восстановление QR кода
|
|
||||||
let inputText = localStorage.getItem('userText');
|
|
||||||
if (inputText) {
|
|
||||||
textInput.value = inputText;
|
|
||||||
generateQR()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Изменение цвета текста в кнопках
|
|
||||||
function changeButtonsColor(color) {
|
|
||||||
const buttons = document.getElementsByTagName('button');
|
|
||||||
for (let index = 0; index < buttons.length; index++) {
|
|
||||||
const btn = buttons[index];
|
|
||||||
btn.style.color = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Изменение темы сайта при клике
|
|
||||||
checkbox.addEventListener('change', () => {
|
|
||||||
const theme = localStorage.getItem('theme')
|
|
||||||
if (theme == 'dark') {
|
|
||||||
createTheme('light')
|
|
||||||
localStorage.setItem('theme', 'light')
|
|
||||||
} else {
|
|
||||||
createTheme('dark')
|
|
||||||
localStorage.setItem('theme', 'dark')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// Вывести сообщение пользователю
|
|
||||||
function logMessage(message, color = 'red') {
|
|
||||||
textError.innerText = message;
|
|
||||||
textError.style.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция для генерации QR-кода
|
|
||||||
function generateQR() {
|
|
||||||
const userText = textInput.value.trim();
|
|
||||||
if (!userText) {
|
|
||||||
logMessage('Введите текст для генерации QR-кода!');
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
localStorage.setItem('userText', userText);
|
|
||||||
qrCodeDiv.innerHTML = '';
|
|
||||||
QRCode.toCanvas(userText, (error, canvas) => {
|
|
||||||
if (error) {
|
|
||||||
console.error(error);
|
|
||||||
logMessage('Ошибка при генерации QR-кода.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qrCodeDiv.appendChild(canvas);
|
|
||||||
logMessage('');
|
|
||||||
canvas.style.transform = `scale(${scale})`;
|
|
||||||
canvas.style.transformOrigin = 'top';
|
|
||||||
})
|
|
||||||
|
|
||||||
const canvas = qrCodeDiv.querySelector('canvas');
|
|
||||||
let canvasWidth = canvas.getBoundingClientRect().width;
|
|
||||||
if (canvasWidth > window.innerWidth * 0.8) {
|
|
||||||
scale = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Генерация QR-кода
|
|
||||||
generateBtn.addEventListener('click', generateQR);
|
|
||||||
|
|
||||||
// Скачивание QR-кода
|
|
||||||
downloadQrBtn.addEventListener('click', () => {
|
|
||||||
let userText = textInput.value.slice(0, 20)
|
|
||||||
const canvas = qrCodeDiv.querySelector('canvas');
|
|
||||||
if (!canvas) {
|
|
||||||
logMessage('QR-код не сгенерирован!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!userText) {
|
|
||||||
logMessage('Отсутствует текст в поле для ввода');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = canvas.toDataURL('image/png');
|
|
||||||
link.download = `qr-code-${userText}.png`;
|
|
||||||
link.click();
|
|
||||||
logMessage('QR-код скачан!', 'green');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Удаление данных
|
|
||||||
delBtn.addEventListener('click', () => {
|
|
||||||
textInput.value = '';
|
|
||||||
qrCodeDiv.innerHTML = '';
|
|
||||||
textError.innerText = '';
|
|
||||||
scale = 1;
|
|
||||||
localStorage.removeItem('userText');
|
|
||||||
localStorage.removeItem('scale');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Изменить значение шкалы
|
|
||||||
function chengeScale(scale) {
|
|
||||||
const canvas = qrCodeDiv.querySelector('canvas');
|
|
||||||
logMessage('')
|
|
||||||
canvas.style.transform = `scale(${scale})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Увеличение QR-кода
|
|
||||||
zoomInBtn.addEventListener('click', () => {
|
|
||||||
const canvas = qrCodeDiv.querySelector('canvas');
|
|
||||||
if (!canvas) {
|
|
||||||
logMessage('До генерации QR кода маштаб менять нельзя')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem('scale', scale)
|
|
||||||
let canvasWidth = canvas.getBoundingClientRect().width;
|
|
||||||
if (canvasWidth + 0.1 < (window.innerWidth * 0.8)) {
|
|
||||||
scale += 0.1; // Увеличить масштаб на 10%
|
|
||||||
chengeScale(scale)
|
|
||||||
} else {
|
|
||||||
logMessage('Достигнут максимальный размер QR кода')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Уменьшение QR-кода
|
|
||||||
zoomOutBtn.addEventListener('click', () => {
|
|
||||||
const canvas = qrCodeDiv.querySelector('canvas');
|
|
||||||
if (!canvas) {
|
|
||||||
logMessage('До генерации QR кода маштаб менять нельзя')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem('scale', scale)
|
|
||||||
let canvasWidth = canvas.getBoundingClientRect().width;
|
|
||||||
if (canvasWidth > window.innerWidth * 0.8) {
|
|
||||||
scale = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scale > 0.5) {
|
|
||||||
scale -= 0.1; // Уменьшить масштаб на 10%
|
|
||||||
chengeScale(scale)
|
|
||||||
} else {
|
|
||||||
logMessage('Достигнут минимальный размер QR кода')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useTheme } from './composables/useTheme'
|
||||||
|
import { useQrCode } from './composables/useQrCode'
|
||||||
|
|
||||||
|
const { isLightTheme, toggleTheme } = useTheme()
|
||||||
|
|
||||||
|
const {
|
||||||
|
textInput,
|
||||||
|
error,
|
||||||
|
errorColor,
|
||||||
|
hasQr,
|
||||||
|
qrCanvasRef,
|
||||||
|
generateQR,
|
||||||
|
downloadQR,
|
||||||
|
clearQR,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
restoreFromStorage,
|
||||||
|
} = useQrCode()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
restoreFromStorage()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="logo">
|
||||||
|
<h1>Генератор QR-кодов</h1>
|
||||||
|
<a href="https://github.com/IliaMiheev/iQR" target="_blank" title="Github">
|
||||||
|
<img src="/img/fluidicon.png" alt="GitHub" class="github-icon" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inputs">
|
||||||
|
<textarea
|
||||||
|
v-model="textInput"
|
||||||
|
cols="30"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Текст для генерации"
|
||||||
|
/>
|
||||||
|
<div class="themeSwitch">
|
||||||
|
<div class="block" title="Изменить тему">
|
||||||
|
<input
|
||||||
|
id="theme-toggle"
|
||||||
|
type="checkbox"
|
||||||
|
:checked="isLightTheme"
|
||||||
|
@change="toggleTheme"
|
||||||
|
/>
|
||||||
|
<label for="theme-toggle" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btns btns1">
|
||||||
|
<button class="btn" title="Создать QR-код" @click="generateQR">Создать</button>
|
||||||
|
<button class="btn" title="Скачать QR-код" @click="downloadQR">Скачать</button>
|
||||||
|
<button class="btn3" title="Очистить поля" @click="clearQR">Очистить</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btns btns2">
|
||||||
|
<button title="Увеличить размер QR кода" @click="zoomIn">+</button>
|
||||||
|
<button title="Уменьшить размер QR кода" @click="zoomOut">-</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="error" :style="{ color: errorColor }">{{ error }}</p>
|
||||||
|
|
||||||
|
<div id="qr-code">
|
||||||
|
<canvas v-show="hasQr" ref="qrCanvasRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.github-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,8 +4,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
cursor: url(/img/Orange_cursor.png), default !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
background-color: #1e1e1e;
|
background-color: #1e1e1e;
|
||||||
cursor: url(img/Orange_cursor.png), default !important
|
}
|
||||||
|
|
||||||
|
html.light {
|
||||||
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -14,6 +21,10 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -26,11 +37,11 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
cursor: url(img/Orange_pointer.png), pointer !important;
|
cursor: url(/img/Orange_pointer.png), pointer !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
cursor: url(img/Orange_cursor.png), pointer !important;
|
cursor: url(/img/Orange_cursor.png), pointer !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputs {
|
.inputs {
|
||||||
@@ -47,7 +58,6 @@ button {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Начало стилей для переключателя темы */
|
|
||||||
.themeSwitch input[type="checkbox"] {
|
.themeSwitch input[type="checkbox"] {
|
||||||
display: none;
|
display: none;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -75,7 +85,7 @@ button {
|
|||||||
transition: .3s ease;
|
transition: .3s ease;
|
||||||
color: black;
|
color: black;
|
||||||
background: white;
|
background: white;
|
||||||
cursor: url(img/Orange_pointer.png), pointer !important;
|
cursor: url(/img/Orange_pointer.png), pointer !important;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +113,6 @@ button {
|
|||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Конец стилей для переключателя темы */
|
|
||||||
|
|
||||||
.btns {
|
.btns {
|
||||||
margin: 7px auto;
|
margin: 7px auto;
|
||||||
width: 60%;
|
width: 60%;
|
||||||
@@ -133,7 +141,7 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn3 {
|
.btn3 {
|
||||||
grid-column: 1 / -1; /* Занимает обе колонки */
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,15 +161,22 @@ textarea::selection {
|
|||||||
|
|
||||||
input[type='checkbox'] {
|
input[type='checkbox'] {
|
||||||
accent-color: orangered;
|
accent-color: orangered;
|
||||||
cursor: url(img/Orange_pointer.png), pointer !important;
|
cursor: url(/img/Orange_pointer.png), pointer !important;
|
||||||
margin: auto 0;
|
margin: auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: orangered;
|
background-color: orangered;
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
cursor: url(img/Orange_pointer.png), pointer !important;
|
cursor: url(/img/Orange_pointer.png), pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark button {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.light button {
|
||||||
|
color: #1e1e1e;
|
||||||
}
|
}
|
||||||
|
|
||||||
button::selection {
|
button::selection {
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
import { ref, nextTick } from 'vue'
|
||||||
|
import QRCode from 'qrcode'
|
||||||
|
|
||||||
|
export function useQrCode() {
|
||||||
|
const textInput = ref('')
|
||||||
|
const error = ref('')
|
||||||
|
const errorColor = ref('red')
|
||||||
|
const scale = ref(1)
|
||||||
|
const hasQr = ref(false)
|
||||||
|
const qrCanvasRef = ref(null)
|
||||||
|
|
||||||
|
function logMessage(message, color = 'red') {
|
||||||
|
error.value = message
|
||||||
|
errorColor.value = color
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyScale() {
|
||||||
|
const canvas = qrCanvasRef.value
|
||||||
|
if (!canvas) return
|
||||||
|
canvas.style.transform = `scale(${scale.value})`
|
||||||
|
canvas.style.transformOrigin = 'top'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateQR() {
|
||||||
|
const userText = textInput.value.trim()
|
||||||
|
if (!userText) {
|
||||||
|
logMessage('Введите текст для генерации QR-кода!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('userText', userText)
|
||||||
|
hasQr.value = true
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await QRCode.toCanvas(qrCanvasRef.value, userText)
|
||||||
|
logMessage('')
|
||||||
|
applyScale()
|
||||||
|
|
||||||
|
const canvasWidth = qrCanvasRef.value.getBoundingClientRect().width
|
||||||
|
if (canvasWidth > window.innerWidth * 0.8) {
|
||||||
|
scale.value = 1
|
||||||
|
applyScale()
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
logMessage('Ошибка при генерации QR-кода.')
|
||||||
|
hasQr.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadQR() {
|
||||||
|
const userText = textInput.value.slice(0, 20)
|
||||||
|
const canvas = qrCanvasRef.value
|
||||||
|
|
||||||
|
if (!canvas) {
|
||||||
|
logMessage('QR-код не сгенерирован!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!userText) {
|
||||||
|
logMessage('Отсутствует текст в поле для ввода')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = canvas.toDataURL('image/png')
|
||||||
|
link.download = `qr-code-${userText}.png`
|
||||||
|
link.click()
|
||||||
|
logMessage('QR-код скачан!', 'green')
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearQR() {
|
||||||
|
textInput.value = ''
|
||||||
|
hasQr.value = false
|
||||||
|
error.value = ''
|
||||||
|
scale.value = 1
|
||||||
|
localStorage.removeItem('userText')
|
||||||
|
localStorage.removeItem('scale')
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomIn() {
|
||||||
|
const canvas = qrCanvasRef.value
|
||||||
|
if (!canvas) {
|
||||||
|
logMessage('До генерации QR кода маштаб менять нельзя')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('scale', scale.value)
|
||||||
|
const canvasWidth = canvas.getBoundingClientRect().width
|
||||||
|
|
||||||
|
if (canvasWidth + 0.1 < window.innerWidth * 0.8) {
|
||||||
|
scale.value += 0.1
|
||||||
|
applyScale()
|
||||||
|
} else {
|
||||||
|
logMessage('Достигнут максимальный размер QR кода')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomOut() {
|
||||||
|
const canvas = qrCanvasRef.value
|
||||||
|
if (!canvas) {
|
||||||
|
logMessage('До генерации QR кода маштаб менять нельзя')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('scale', scale.value)
|
||||||
|
const canvasWidth = canvas.getBoundingClientRect().width
|
||||||
|
|
||||||
|
if (canvasWidth > window.innerWidth * 0.8) {
|
||||||
|
scale.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scale.value > 0.5) {
|
||||||
|
scale.value -= 0.1
|
||||||
|
applyScale()
|
||||||
|
} else {
|
||||||
|
logMessage('Достигнут минимальный размер QR кода')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreFromStorage() {
|
||||||
|
const savedScale = localStorage.getItem('scale')
|
||||||
|
if (savedScale) {
|
||||||
|
scale.value = Number(savedScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedText = localStorage.getItem('userText')
|
||||||
|
if (savedText) {
|
||||||
|
textInput.value = savedText
|
||||||
|
await generateQR()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
textInput,
|
||||||
|
error,
|
||||||
|
errorColor,
|
||||||
|
scale,
|
||||||
|
hasQr,
|
||||||
|
qrCanvasRef,
|
||||||
|
generateQR,
|
||||||
|
downloadQR,
|
||||||
|
clearQR,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
restoreFromStorage,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const theme = ref('dark')
|
||||||
|
|
||||||
|
const isLightTheme = computed(() => theme.value === 'light')
|
||||||
|
|
||||||
|
function applyTheme(name) {
|
||||||
|
theme.value = name
|
||||||
|
document.documentElement.className = name
|
||||||
|
localStorage.setItem('theme', name)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
applyTheme(theme.value === 'dark' ? 'light' : 'dark')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const saved = localStorage.getItem('theme')
|
||||||
|
if (saved === 'dark' || saved === 'light') {
|
||||||
|
applyTheme(saved)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
applyTheme(prefersDark ? 'dark' : 'light')
|
||||||
|
})
|
||||||
|
|
||||||
|
return { theme, isLightTheme, toggleTheme }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import './assets/style.css'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
base: '/',
|
||||||
|
})
|
||||||