Переписал на vue3
@@ -1,2 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
.DS_Store
|
||||
*.local
|
||||
__pycache__
|
||||
ideas.md
|
||||
|
||||
@@ -1,9 +1,40 @@
|
||||
## [Сайт](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>
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<html lang="ru" class="dark">
|
||||
<head>
|
||||
<link rel="icon" href="./img/favicon.ico" type="image/x-icon">
|
||||
<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">
|
||||
<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="description" content="Простой в использовании генератор QR кодов. Сделано программистом с душой">
|
||||
<meta name="keywords" content="QR, QR код, iQR">
|
||||
@@ -17,62 +13,25 @@
|
||||
<meta name="robots" content="all">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="revisit-after" content="8 days">
|
||||
<meta name="description" content="Russian">
|
||||
<meta name="author" content="Ilia Miheev">
|
||||
<meta name="theme-color" content="orangered">
|
||||
|
||||
<!-- Open Graph Tags -->
|
||||
<meta property="og:title" content="Генератор QR кодов">
|
||||
<meta property="og:description" content="Простой в использовании генератор QR кодов. Сделано программистом с любовью к пользователям">
|
||||
<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:type" content="website">
|
||||
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<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 });
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<div><img src="https://mc.yandex.ru/watch/103902084" style="position:absolute; left:-9999px;" alt="" /></div>
|
||||
</noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
</head>
|
||||
|
||||
<!-- Разработчик: Илья) -->
|
||||
|
||||
<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>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
</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 {
|
||||
cursor: url(/img/Orange_cursor.png), default !important;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
background-color: #1e1e1e;
|
||||
cursor: url(img/Orange_cursor.png), default !important
|
||||
}
|
||||
|
||||
html.light {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -14,6 +21,10 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -26,11 +37,11 @@ h1 {
|
||||
}
|
||||
|
||||
img {
|
||||
cursor: url(img/Orange_pointer.png), pointer !important;
|
||||
cursor: url(/img/Orange_pointer.png), pointer !important;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: url(img/Orange_cursor.png), pointer !important;
|
||||
cursor: url(/img/Orange_cursor.png), pointer !important;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
@@ -47,7 +58,6 @@ button {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Начало стилей для переключателя темы */
|
||||
.themeSwitch input[type="checkbox"] {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
@@ -75,7 +85,7 @@ button {
|
||||
transition: .3s ease;
|
||||
color: black;
|
||||
background: white;
|
||||
cursor: url(img/Orange_pointer.png), pointer !important;
|
||||
cursor: url(/img/Orange_pointer.png), pointer !important;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -103,8 +113,6 @@ button {
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
/* Конец стилей для переключателя темы */
|
||||
|
||||
.btns {
|
||||
margin: 7px auto;
|
||||
width: 60%;
|
||||
@@ -120,7 +128,7 @@ button {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.btns2 button{
|
||||
.btns2 button {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
@@ -133,7 +141,7 @@ button {
|
||||
}
|
||||
|
||||
.btn3 {
|
||||
grid-column: 1 / -1; /* Занимает обе колонки */
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,15 +161,22 @@ textarea::selection {
|
||||
|
||||
input[type='checkbox'] {
|
||||
accent-color: orangered;
|
||||
cursor: url(img/Orange_pointer.png), pointer !important;
|
||||
cursor: url(/img/Orange_pointer.png), pointer !important;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: orangered;
|
||||
color: #fff;
|
||||
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 {
|
||||
@@ -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: '/',
|
||||
})
|
||||