vacancy detail meta
This commit is contained in:
@@ -3,11 +3,80 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Вакансия | Rabota.Today</title>
|
|
||||||
|
<!-- Базовые SEO теги -->
|
||||||
|
<title id="pageTitle">Вакансия | Rabota.Today</title>
|
||||||
|
<meta name="description" id="metaDescription" content="Подробная информация о вакансии на Rabota.Today. Зарплата, требования, условия работы, контакты работодателя.">
|
||||||
|
<meta name="keywords" content="вакансия, работа, поиск работы, Rabota.Today, трудоустройство">
|
||||||
|
<meta name="author" content="Rabota.Today">
|
||||||
|
<meta name="robots" content="index, follow">
|
||||||
|
|
||||||
|
<!-- Open Graph / Facebook -->
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" id="ogUrl" content="https://yarmarka.rabota.today/">
|
||||||
|
<meta property="og:title" id="ogTitle" content="Вакансия | Rabota.Today">
|
||||||
|
<meta property="og:description" id="ogDescription" content="Подробная информация о вакансии на Rabota.Today">
|
||||||
|
<meta property="og:image" id="ogImage" content="https://yarmarka.rabota.today/static/images/og-image.jpg">
|
||||||
|
<meta property="og:site_name" content="Rabota.Today">
|
||||||
|
<meta property="og:locale" content="ru_RU">
|
||||||
|
|
||||||
|
<!-- Twitter Card -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:title" id="twitterTitle" content="Вакансия | Rabota.Today">
|
||||||
|
<meta name="twitter:description" id="twitterDescription" content="Подробная информация о вакансии на Rabota.Today">
|
||||||
|
<meta name="twitter:image" id="twitterImage" content="https://yarmarka.rabota.today/static/images/og-image.jpg">
|
||||||
|
|
||||||
|
<!-- Canonical URL -->
|
||||||
|
<link rel="canonical" id="canonicalUrl" href="https://yarmarka.rabota.today/">
|
||||||
|
|
||||||
|
<!-- Дополнительные SEO метатеги -->
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<meta name="theme-color" content="#0b1c34">
|
||||||
|
|
||||||
|
<!-- Структурированные данные (JSON-LD) -->
|
||||||
|
<script type="application/ld+json" id="structuredData">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "JobPosting",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"datePosted": "",
|
||||||
|
"validThrough": "",
|
||||||
|
"employmentType": "FULL_TIME",
|
||||||
|
"hiringOrganization": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "",
|
||||||
|
"sameAs": "",
|
||||||
|
"logo": "https://yarmarka.rabota.today/static/images/logo.png"
|
||||||
|
},
|
||||||
|
"jobLocation": {
|
||||||
|
"@type": "Place",
|
||||||
|
"address": {
|
||||||
|
"@type": "PostalAddress",
|
||||||
|
"addressLocality": "Москва",
|
||||||
|
"addressCountry": "RU"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"baseSalary": {
|
||||||
|
"@type": "MonetaryAmount",
|
||||||
|
"currency": "RUB",
|
||||||
|
"value": {
|
||||||
|
"@type": "QuantitativeValue",
|
||||||
|
"value": 0,
|
||||||
|
"unitText": "MONTH"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"employmentType": "FULL_TIME",
|
||||||
|
"workHours": "Полный день"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||||
<!-- Подключаем библиотеку для генерации QR-кодов -->
|
<!-- Подключаем библиотеку для генерации QR-кодов -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* ... все существующие стили ... */
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -475,11 +544,13 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(0,0,0,0.6);
|
background: rgba(0,0,0,0.7);
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-modal.active {
|
.qr-modal.active {
|
||||||
@@ -487,11 +558,6 @@
|
|||||||
animation: fadeIn 0.3s;
|
animation: fadeIn 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-modal-content {
|
.qr-modal-content {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
@@ -501,17 +567,17 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: 0 30px 60px rgba(0,0,0,0.3);
|
box-shadow: 0 30px 60px rgba(0,0,0,0.3);
|
||||||
animation: slideUp 0.3s;
|
animation: slideUp 0.3s;
|
||||||
}
|
margin: auto;
|
||||||
|
max-height: 90vh;
|
||||||
@keyframes slideUp {
|
overflow-y: auto;
|
||||||
from { transform: translateY(30px); opacity: 0; }
|
|
||||||
to { transform: translateY(0); opacity: 1; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-modal-close {
|
.qr-modal-close {
|
||||||
position: absolute;
|
position: sticky;
|
||||||
top: 20px;
|
top: 0;
|
||||||
right: 20px;
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@@ -524,21 +590,30 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #4f7092;
|
color: #4f7092;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-modal-close:hover {
|
.qr-modal-close:hover {
|
||||||
background: #dbeafe;
|
background: #dbeafe;
|
||||||
color: #0b1c34;
|
color: #0b1c34;
|
||||||
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-modal h2 {
|
.qr-modal h2 {
|
||||||
color: #0b1c34;
|
color: #0b1c34;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 28px;
|
font-size: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
padding-right: 40px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: white;
|
||||||
|
padding-top: 0;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-modal h2 i {
|
.qr-modal h2 i {
|
||||||
@@ -605,10 +680,13 @@
|
|||||||
padding: 15px;
|
padding: 15px;
|
||||||
background: #f0f7ff;
|
background: #f0f7ff;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-stat-item {
|
.qr-stat-item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
min-width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-stat-value {
|
.qr-stat-value {
|
||||||
@@ -626,7 +704,7 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin: 25px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-action-btn {
|
.qr-action-btn {
|
||||||
@@ -672,6 +750,7 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
border-top: 1px solid #dee9f5;
|
border-top: 1px solid #dee9f5;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-share-btn {
|
.qr-share-btn {
|
||||||
@@ -757,73 +836,25 @@
|
|||||||
to { transform: translateX(0%); opacity: 1; }
|
to { transform: translateX(0%); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Улучшенные стили для модального окна с прокруткой */
|
@keyframes fadeIn {
|
||||||
.qr-modal {
|
from { opacity: 0; }
|
||||||
display: none;
|
to { opacity: 1; }
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(0,0,0,0.7);
|
|
||||||
align-items: flex-start; /* Изменено с center на flex-start */
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1000;
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
overflow-y: auto; /* Добавляем прокрутку для всего модального окна */
|
|
||||||
padding: 20px 0; /* Отступы сверху и снизу */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-modal.active {
|
@keyframes slideUp {
|
||||||
display: flex;
|
from { transform: translateY(30px); opacity: 0; }
|
||||||
animation: fadeIn 0.3s;
|
to { transform: translateY(0); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-modal-content {
|
|
||||||
background: white;
|
|
||||||
border-radius: 40px;
|
|
||||||
padding: 40px;
|
|
||||||
max-width: 500px;
|
|
||||||
width: 90%;
|
|
||||||
position: relative;
|
|
||||||
box-shadow: 0 30px 60px rgba(0,0,0,0.3);
|
|
||||||
animation: slideUp 0.3s;
|
|
||||||
margin: auto; /* Центрирование по вертикали при возможности */
|
|
||||||
max-height: 90vh; /* Ограничиваем высоту */
|
|
||||||
overflow-y: auto; /* Внутренняя прокрутка если контент не помещается */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Улучшенная кнопка закрытия - всегда видима */
|
|
||||||
.qr-modal-close {
|
|
||||||
position: sticky; /* Меняем с absolute на sticky */
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 20px;
|
|
||||||
background: #eef4fa;
|
|
||||||
border: none;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #4f7092;
|
|
||||||
transition: 0.2s;
|
|
||||||
z-index: 10;
|
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-modal-close:hover {
|
|
||||||
background: #dbeafe;
|
|
||||||
color: #0b1c34;
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Фиксированная кнопка закрытия для мобильных */
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
.vacancy-title {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vacancy-salary {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.qr-modal-content {
|
.qr-modal-content {
|
||||||
padding: 30px 20px;
|
padding: 30px 20px;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
@@ -837,161 +868,6 @@
|
|||||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Заголовок с фиксацией */
|
|
||||||
.qr-modal h2 {
|
|
||||||
color: #0b1c34;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 24px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
word-break: break-word;
|
|
||||||
padding-right: 40px; /* Место для кнопки закрытия */
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
background: white;
|
|
||||||
padding-top: 0;
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Контейнер для QR с центрированием */
|
|
||||||
.qr-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 20px 0;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f9fcff;
|
|
||||||
border-radius: 30px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Статистика */
|
|
||||||
.qr-stats {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
margin: 20px 0;
|
|
||||||
padding: 15px;
|
|
||||||
background: #f0f7ff;
|
|
||||||
border-radius: 20px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-stat-item {
|
|
||||||
text-align: center;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-stat-value {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #0b1c34;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-stat-label {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #4f7092;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Сетка действий */
|
|
||||||
.qr-actions {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-action-btn {
|
|
||||||
padding: 14px;
|
|
||||||
border-radius: 30px;
|
|
||||||
border: none;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
transition: 0.2s;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-action-btn.primary {
|
|
||||||
background: #0b1c34;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-action-btn.primary:hover {
|
|
||||||
background: #1b3f6b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-action-btn.secondary {
|
|
||||||
background: #eef4fa;
|
|
||||||
color: #1f3f60;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-action-btn.secondary:hover {
|
|
||||||
background: #dbeafe;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-action-btn i {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Кнопки шеринга */
|
|
||||||
.qr-share-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 20px;
|
|
||||||
border-top: 1px solid #dee9f5;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-share-btn {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
border-radius: 22px;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-share-btn.whatsapp {
|
|
||||||
background: #25D366;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-share-btn.telegram {
|
|
||||||
background: #0088cc;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-share-btn.email {
|
|
||||||
background: #ea4335;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-share-btn:hover {
|
|
||||||
transform: translateY(-3px);
|
|
||||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Анимации */
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideUp {
|
|
||||||
from { transform: translateY(30px); opacity: 0; }
|
|
||||||
to { transform: translateY(0); opacity: 1; }
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -999,7 +875,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="logo" onclick="window.location.href='/'">
|
<div class="logo" onclick="window.location.href='/'">
|
||||||
<i class="fas fa-briefcase"></i>
|
<i class="fas fa-briefcase"></i>
|
||||||
МП.Ярмарка
|
Rabota.Today
|
||||||
</div>
|
</div>
|
||||||
<div class="nav" id="nav">
|
<div class="nav" id="nav">
|
||||||
<!-- Навигация будет заполнена динамически -->
|
<!-- Навигация будет заполнена динамически -->
|
||||||
@@ -1104,6 +980,82 @@
|
|||||||
.replace(/'/g, "'");
|
.replace(/'/g, "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для обновления SEO тегов
|
||||||
|
function updateSEOTags(vacancy) {
|
||||||
|
const decodedTitle = decodeHtmlEntities(vacancy.title);
|
||||||
|
const decodedCompany = decodeHtmlEntities(vacancy.company_name || 'Компания');
|
||||||
|
const salary = vacancy.salary || 'Зарплата не указана';
|
||||||
|
const description = vacancy.description || 'Подробная информация о вакансии';
|
||||||
|
|
||||||
|
// Формируем описание для SEO
|
||||||
|
const shortDescription = description.length > 160 ? description.substring(0, 157) + '...' : description;
|
||||||
|
const seoDescription = `${decodedTitle} в компании ${decodedCompany}. ${salary}. ${shortDescription}`;
|
||||||
|
|
||||||
|
// Заголовок страницы
|
||||||
|
document.title = `${decodedTitle} в ${decodedCompany} | Rabota.Today`;
|
||||||
|
|
||||||
|
// Мета-теги
|
||||||
|
document.querySelector('meta[name="description"]')?.setAttribute('content', seoDescription);
|
||||||
|
document.querySelector('meta[name="keywords"]')?.setAttribute('content', `${decodedTitle}, работа, вакансия, ${decodedCompany}, трудоустройство, поиск работы`);
|
||||||
|
|
||||||
|
// Open Graph теги
|
||||||
|
document.querySelector('meta[property="og:title"]')?.setAttribute('content', `${decodedTitle} в ${decodedCompany}`);
|
||||||
|
document.querySelector('meta[property="og:description"]')?.setAttribute('content', seoDescription);
|
||||||
|
document.querySelector('meta[property="og:url"]')?.setAttribute('content', window.location.href);
|
||||||
|
|
||||||
|
// Twitter Card
|
||||||
|
document.querySelector('meta[name="twitter:title"]')?.setAttribute('content', `${decodedTitle} в ${decodedCompany}`);
|
||||||
|
document.querySelector('meta[name="twitter:description"]')?.setAttribute('content', seoDescription);
|
||||||
|
|
||||||
|
// Canonical URL
|
||||||
|
document.querySelector('link[rel="canonical"]')?.setAttribute('href', window.location.href);
|
||||||
|
|
||||||
|
// Структурированные данные (JSON-LD)
|
||||||
|
const salaryMatch = salary.match(/(\d+)/);
|
||||||
|
const salaryValue = salaryMatch ? parseInt(salaryMatch[0]) : 0;
|
||||||
|
|
||||||
|
const structuredData = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "JobPosting",
|
||||||
|
"title": decodedTitle,
|
||||||
|
"description": description,
|
||||||
|
"datePosted": vacancy.created_at,
|
||||||
|
"validThrough": new Date(new Date(vacancy.created_at).getTime() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||||
|
"employmentType": "FULL_TIME",
|
||||||
|
"hiringOrganization": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": decodedCompany,
|
||||||
|
"sameAs": vacancy.company_website || "",
|
||||||
|
"logo": vacancy.company_logo || "https://yarmarka.rabota.today/static/images/logo.png"
|
||||||
|
},
|
||||||
|
"jobLocation": {
|
||||||
|
"@type": "Place",
|
||||||
|
"address": {
|
||||||
|
"@type": "PostalAddress",
|
||||||
|
"addressLocality": vacancy.company_address || "Москва",
|
||||||
|
"addressCountry": "RU"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"baseSalary": {
|
||||||
|
"@type": "MonetaryAmount",
|
||||||
|
"currency": "RUB",
|
||||||
|
"value": {
|
||||||
|
"@type": "QuantitativeValue",
|
||||||
|
"value": salaryValue,
|
||||||
|
"unitText": "MONTH"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workHours": "Полный день"
|
||||||
|
};
|
||||||
|
|
||||||
|
const scriptElement = document.getElementById('structuredData');
|
||||||
|
if (scriptElement) {
|
||||||
|
scriptElement.textContent = JSON.stringify(structuredData, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ SEO теги обновлены');
|
||||||
|
}
|
||||||
|
|
||||||
// Проверка авторизации
|
// Проверка авторизации
|
||||||
async function checkAuth() {
|
async function checkAuth() {
|
||||||
const token = localStorage.getItem('accessToken');
|
const token = localStorage.getItem('accessToken');
|
||||||
@@ -1175,6 +1127,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentVacancy = await response.json();
|
currentVacancy = await response.json();
|
||||||
|
|
||||||
|
// Обновляем SEO теги
|
||||||
|
updateSEOTags(currentVacancy);
|
||||||
|
|
||||||
renderVacancy(currentVacancy);
|
renderVacancy(currentVacancy);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1191,7 +1147,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отображение вакансии с QR-кнопкой
|
// Отображение вакансии
|
||||||
function renderVacancy(vacancy) {
|
function renderVacancy(vacancy) {
|
||||||
const container = document.getElementById('vacancyDetail');
|
const container = document.getElementById('vacancyDetail');
|
||||||
const token = localStorage.getItem('accessToken');
|
const token = localStorage.getItem('accessToken');
|
||||||
@@ -1346,7 +1302,6 @@
|
|||||||
function openQRModal() {
|
function openQRModal() {
|
||||||
if (!currentVacancy) return;
|
if (!currentVacancy) return;
|
||||||
|
|
||||||
// Декодируем названия
|
|
||||||
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
|
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
|
||||||
const decodedCompanyName = decodeHtmlEntities(currentVacancy.company_name || '');
|
const decodedCompanyName = decodeHtmlEntities(currentVacancy.company_name || '');
|
||||||
|
|
||||||
@@ -1355,43 +1310,31 @@
|
|||||||
const vacancyUrl = window.location.origin + '/vacancy/' + vacancyId;
|
const vacancyUrl = window.location.origin + '/vacancy/' + vacancyId;
|
||||||
document.getElementById('qrVacancyUrl').textContent = vacancyUrl.replace('https://', '').replace('http://', '');
|
document.getElementById('qrVacancyUrl').textContent = vacancyUrl.replace('https://', '').replace('http://', '');
|
||||||
|
|
||||||
// Обновляем статистику
|
|
||||||
document.getElementById('qrViewCount').textContent = ++qrViewCount;
|
document.getElementById('qrViewCount').textContent = ++qrViewCount;
|
||||||
document.getElementById('qrViewBadge').textContent = qrViewCount;
|
document.getElementById('qrViewBadge').textContent = qrViewCount;
|
||||||
|
|
||||||
// Информация о зарплате
|
|
||||||
document.getElementById('qrSalaryInfo').textContent = currentVacancy.salary || 'з/п не указана';
|
document.getElementById('qrSalaryInfo').textContent = currentVacancy.salary || 'з/п не указана';
|
||||||
|
|
||||||
// Название компании
|
|
||||||
document.getElementById('qrCompanyName').textContent = decodedCompanyName || '—';
|
document.getElementById('qrCompanyName').textContent = decodedCompanyName || '—';
|
||||||
|
|
||||||
// Генерируем QR-код
|
|
||||||
generateQRCodeWithLogo(vacancyUrl, currentVacancy.company_logo);
|
generateQRCodeWithLogo(vacancyUrl, currentVacancy.company_logo);
|
||||||
|
|
||||||
document.getElementById('qrModal').classList.add('active');
|
document.getElementById('qrModal').classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Закрыть модальное окно
|
|
||||||
function closeQRModal() {
|
function closeQRModal() {
|
||||||
document.getElementById('qrModal').classList.remove('active');
|
document.getElementById('qrModal').classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Генерация QR-кода с логотипом компании
|
|
||||||
function generateQRCodeWithLogo(text, logoUrl) {
|
function generateQRCodeWithLogo(text, logoUrl) {
|
||||||
const canvas = document.getElementById('qrCanvas');
|
const canvas = document.getElementById('qrCanvas');
|
||||||
const logoOverlay = document.getElementById('qrLogoOverlay');
|
const logoOverlay = document.getElementById('qrLogoOverlay');
|
||||||
const logoImg = document.getElementById('qrLogoImg');
|
const logoImg = document.getElementById('qrLogoImg');
|
||||||
const logoIcon = document.getElementById('qrLogoIcon');
|
const logoIcon = document.getElementById('qrLogoIcon');
|
||||||
|
|
||||||
// Настройки QR-кода
|
|
||||||
const options = {
|
const options = {
|
||||||
width: 250,
|
width: 250,
|
||||||
height: 250,
|
height: 250,
|
||||||
color: {
|
color: { dark: '#0b1c34', light: '#ffffff' },
|
||||||
dark: '#0b1c34',
|
errorCorrectionLevel: 'H'
|
||||||
light: '#ffffff'
|
|
||||||
},
|
|
||||||
errorCorrectionLevel: 'H' // высокая коррекция ошибок для логотипа
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QRCode.toCanvas(canvas, text, options, function(error) {
|
QRCode.toCanvas(canvas, text, options, function(error) {
|
||||||
@@ -1399,25 +1342,18 @@
|
|||||||
console.error('Error generating QR code:', error);
|
console.error('Error generating QR code:', error);
|
||||||
showNotification('Ошибка генерации QR-кода', 'error');
|
showNotification('Ошибка генерации QR-кода', 'error');
|
||||||
} else {
|
} else {
|
||||||
console.log('QR code generated successfully');
|
|
||||||
|
|
||||||
// Показываем оверлей с логотипом или иконкой
|
|
||||||
logoOverlay.style.display = 'flex';
|
logoOverlay.style.display = 'flex';
|
||||||
|
|
||||||
if (logoUrl) {
|
if (logoUrl) {
|
||||||
// Если есть логотип компании, показываем его
|
|
||||||
logoImg.src = logoUrl;
|
logoImg.src = logoUrl;
|
||||||
logoImg.style.display = 'block';
|
logoImg.style.display = 'block';
|
||||||
logoIcon.style.display = 'none';
|
logoIcon.style.display = 'none';
|
||||||
|
|
||||||
// Обработка ошибки загрузки логотипа
|
|
||||||
logoImg.onerror = function() {
|
logoImg.onerror = function() {
|
||||||
logoImg.style.display = 'none';
|
logoImg.style.display = 'none';
|
||||||
logoIcon.style.display = 'block';
|
logoIcon.style.display = 'block';
|
||||||
logoIcon.className = 'fas fa-briefcase';
|
logoIcon.className = 'fas fa-briefcase';
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Если нет логотипа, показываем иконку вакансии
|
|
||||||
logoImg.style.display = 'none';
|
logoImg.style.display = 'none';
|
||||||
logoIcon.style.display = 'block';
|
logoIcon.style.display = 'block';
|
||||||
logoIcon.className = 'fas fa-briefcase';
|
logoIcon.className = 'fas fa-briefcase';
|
||||||
@@ -1426,48 +1362,24 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Скачать QR-код
|
|
||||||
function downloadQR() {
|
function downloadQR() {
|
||||||
const canvas = document.getElementById('qrCanvas');
|
const canvas = document.getElementById('qrCanvas');
|
||||||
const logoOverlay = document.getElementById('qrLogoOverlay');
|
|
||||||
const logoImg = document.getElementById('qrLogoImg');
|
|
||||||
const logoIcon = document.getElementById('qrLogoIcon');
|
|
||||||
|
|
||||||
// Создаем временный canvas для объединения QR и логотипа
|
|
||||||
const combinedCanvas = document.createElement('canvas');
|
const combinedCanvas = document.createElement('canvas');
|
||||||
combinedCanvas.width = canvas.width;
|
combinedCanvas.width = canvas.width;
|
||||||
combinedCanvas.height = canvas.height;
|
combinedCanvas.height = canvas.height;
|
||||||
const ctx = combinedCanvas.getContext('2d');
|
const ctx = combinedCanvas.getContext('2d');
|
||||||
|
|
||||||
// Рисуем QR-код
|
|
||||||
ctx.drawImage(canvas, 0, 0);
|
ctx.drawImage(canvas, 0, 0);
|
||||||
|
|
||||||
// Рисуем логотип поверх
|
|
||||||
ctx.fillStyle = 'white';
|
ctx.fillStyle = 'white';
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(125, 125, 35, 0, 2 * Math.PI);
|
ctx.arc(125, 125, 35, 0, 2 * Math.PI);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
if (logoImg.style.display === 'block' && logoImg.complete) {
|
|
||||||
// Если есть загруженный логотип
|
|
||||||
ctx.save();
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(125, 125, 30, 0, 2 * Math.PI);
|
|
||||||
ctx.clip();
|
|
||||||
|
|
||||||
const logoSize = 50;
|
|
||||||
ctx.drawImage(logoImg, 125 - logoSize/2, 125 - logoSize/2, logoSize, logoSize);
|
|
||||||
ctx.restore();
|
|
||||||
} else {
|
|
||||||
// Если нет логотипа, рисуем иконку
|
|
||||||
ctx.fillStyle = '#3b82f6';
|
ctx.fillStyle = '#3b82f6';
|
||||||
ctx.font = '30px "Font Awesome 6 Free"';
|
ctx.font = '30px "Font Awesome 6 Free"';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fillText('', 125, 125); // Unicode для иконки briefcase
|
ctx.fillText('', 125, 125);
|
||||||
}
|
|
||||||
|
|
||||||
// Скачиваем
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
|
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
|
||||||
const filename = `vacancy_${decodedTitle.toLowerCase().replace(/[^a-zа-я0-9]/g, '_')}.png`;
|
const filename = `vacancy_${decodedTitle.toLowerCase().replace(/[^a-zа-я0-9]/g, '_')}.png`;
|
||||||
@@ -1478,7 +1390,6 @@
|
|||||||
showNotification('QR-код скачан', 'success');
|
showNotification('QR-код скачан', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Копировать ссылку на вакансию
|
|
||||||
function copyVacancyLink() {
|
function copyVacancyLink() {
|
||||||
const url = window.location.origin + '/vacancy/' + vacancyId;
|
const url = window.location.origin + '/vacancy/' + vacancyId;
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
navigator.clipboard.writeText(url).then(() => {
|
||||||
@@ -1488,41 +1399,30 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поделиться вакансией
|
|
||||||
function shareVacancy() {
|
function shareVacancy() {
|
||||||
const url = window.location.href;
|
const url = window.location.href;
|
||||||
|
|
||||||
if (navigator.share) {
|
if (navigator.share) {
|
||||||
navigator.share({
|
navigator.share({ title: document.title, url: url }).catch(() => copyVacancyLink());
|
||||||
title: document.title,
|
|
||||||
url: url
|
|
||||||
}).catch(() => {
|
|
||||||
copyVacancyLink();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
copyVacancyLink();
|
copyVacancyLink();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Распечатать QR
|
|
||||||
function printQR() {
|
function printQR() {
|
||||||
const canvas = document.getElementById('qrCanvas');
|
const canvas = document.getElementById('qrCanvas');
|
||||||
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
|
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
|
||||||
const decodedCompany = decodeHtmlEntities(currentVacancy.company_name || '');
|
const decodedCompany = decodeHtmlEntities(currentVacancy.company_name || '');
|
||||||
|
|
||||||
const printWindow = window.open('', '_blank');
|
const printWindow = window.open('', '_blank');
|
||||||
|
|
||||||
printWindow.document.write(`
|
printWindow.document.write(`
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head><title>QR-код вакансии</title>
|
||||||
<title>QR-код вакансии</title>
|
|
||||||
<style>
|
<style>
|
||||||
body { display: flex; justify-content: center; align-items: center; height: 100vh; flex-direction: column; font-family: Arial; margin: 0; padding: 20px; }
|
body { display: flex; justify-content: center; align-items: center; height: 100vh; flex-direction: column; font-family: Arial; margin: 0; padding: 20px; }
|
||||||
h2 { color: #0b1c34; text-align: center; word-break: break-word; }
|
h2 { color: #0b1c34; text-align: center; word-break: break-word; }
|
||||||
h3 { color: #3b82f6; text-align: center; }
|
h3 { color: #3b82f6; text-align: center; }
|
||||||
img { max-width: 300px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); border-radius: 20px; }
|
img { max-width: 300px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); border-radius: 20px; }
|
||||||
.url { color: #4f7092; margin-top: 20px; word-break: break-all; text-align: center; }
|
.url { color: #4f7092; margin-top: 20px; word-break: break-all; text-align: center; }
|
||||||
.company { margin-bottom: 20px; color: #1f3f60; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -1533,54 +1433,34 @@
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
printWindow.document.close();
|
printWindow.document.close();
|
||||||
printWindow.focus();
|
printWindow.focus();
|
||||||
printWindow.print();
|
printWindow.print();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поделиться QR в соцсетях
|
|
||||||
function shareQR(platform) {
|
function shareQR(platform) {
|
||||||
const url = window.location.origin + '/vacancy/' + vacancyId;
|
const url = window.location.origin + '/vacancy/' + vacancyId;
|
||||||
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
|
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
|
||||||
const text = `Вакансия: ${decodedTitle} на Rabota.Today`;
|
const text = `Вакансия: ${decodedTitle} на Rabota.Today`;
|
||||||
|
|
||||||
let shareUrl = '';
|
let shareUrl = '';
|
||||||
|
|
||||||
switch(platform) {
|
switch(platform) {
|
||||||
case 'whatsapp':
|
case 'whatsapp': shareUrl = `https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`; break;
|
||||||
shareUrl = `https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`;
|
case 'telegram': shareUrl = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`; break;
|
||||||
break;
|
case 'email': shareUrl = `mailto:?subject=${encodeURIComponent('Вакансия ' + decodedTitle)}&body=${encodeURIComponent(text + '\n\n' + url)}`; break;
|
||||||
case 'telegram':
|
}
|
||||||
shareUrl = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`;
|
if (shareUrl) window.open(shareUrl, '_blank');
|
||||||
break;
|
|
||||||
case 'email':
|
|
||||||
shareUrl = `mailto:?subject=${encodeURIComponent('Вакансия ' + decodedTitle)}&body=${encodeURIComponent(text + '\n\n' + url)}`;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shareUrl) {
|
|
||||||
window.open(shareUrl, '_blank');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отклик на вакансию
|
|
||||||
async function applyForVacancy() {
|
async function applyForVacancy() {
|
||||||
const token = localStorage.getItem('accessToken');
|
const token = localStorage.getItem('accessToken');
|
||||||
|
if (!token) { redirectToLogin(); return; }
|
||||||
if (!token) {
|
|
||||||
redirectToLogin();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/vacancies/${vacancyId}/apply`, {
|
const response = await fetch(`${API_BASE_URL}/vacancies/${vacancyId}/apply`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
'Authorization': `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showNotification('Отклик отправлен! Работодатель свяжется с вами.', 'success');
|
showNotification('Отклик отправлен! Работодатель свяжется с вами.', 'success');
|
||||||
loadVacancy();
|
loadVacancy();
|
||||||
@@ -1593,52 +1473,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавить в избранное
|
|
||||||
function saveToFavorites() {
|
function saveToFavorites() {
|
||||||
const token = localStorage.getItem('accessToken');
|
const token = localStorage.getItem('accessToken');
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
if (confirm('Для добавления в избранное нужно войти в систему. Перейти на страницу входа?')) {
|
if (confirm('Для добавления в избранное нужно войти в систему. Перейти на страницу входа?')) {
|
||||||
window.location.href = '/login';
|
window.location.href = '/login';
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Здесь будет логика добавления в избранное
|
|
||||||
showNotification('Вакансия добавлена в избранное', 'success');
|
showNotification('Вакансия добавлена в избранное', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Редирект на страницу входа
|
|
||||||
function redirectToLogin() {
|
function redirectToLogin() {
|
||||||
if (confirm('Для отклика нужно войти в систему. Перейти на страницу входа?')) {
|
if (confirm('Для отклика нужно войти в систему. Перейти на страницу входа?')) {
|
||||||
window.location.href = '/login';
|
window.location.href = '/login';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Показать уведомление
|
|
||||||
function showNotification(message, type = 'info') {
|
function showNotification(message, type = 'info') {
|
||||||
const notification = document.getElementById('notification');
|
const notification = document.getElementById('notification');
|
||||||
notification.className = `notification ${type}`;
|
notification.className = `notification ${type}`;
|
||||||
notification.innerHTML = message;
|
notification.innerHTML = message;
|
||||||
notification.style.display = 'block';
|
notification.style.display = 'block';
|
||||||
|
setTimeout(() => { notification.style.display = 'none'; }, 3000);
|
||||||
setTimeout(() => {
|
|
||||||
notification.style.display = 'none';
|
|
||||||
}, 3000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Закрытие модального окна по клику вне
|
|
||||||
window.onclick = function(event) {
|
window.onclick = function(event) {
|
||||||
const modal = document.getElementById('qrModal');
|
const modal = document.getElementById('qrModal');
|
||||||
if (event.target === modal) {
|
if (event.target === modal) modal.classList.remove('active');
|
||||||
modal.classList.remove('active');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Инициализация
|
checkAuth().then(() => loadVacancy());
|
||||||
checkAuth().then(() => {
|
|
||||||
loadVacancy();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user