работа с публикацией

This commit is contained in:
2025-12-20 12:19:33 +03:00
parent e83647042f
commit b1e99277d1
16 changed files with 1344 additions and 165 deletions
+177
View File
@@ -0,0 +1,177 @@
/* Карточки */
.card {
border-radius: 10px;
border: 1px solid #e9ecef;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
}
.card-header {
background-color: rgba(0, 0, 0, 0.02);
border-bottom: 1px solid #e9ecef;
padding: 1rem 1.25rem;
}
/* Таблица сотрудников */
.table-responsive {
max-height: 500px;
overflow-y: auto;
}
.table {
margin-bottom: 0;
}
.table thead th {
position: sticky;
top: 0;
background-color: #f8f9fa;
z-index: 10;
font-weight: 600;
text-transform: uppercase;
font-size: 0.85rem;
color: #6c757d;
border-bottom: 2px solid #dee2e6;
}
.table tbody tr {
transition: all 0.2s ease;
}
.table tbody tr:hover {
background-color: rgba(13, 110, 253, 0.05);
}
.table tbody tr.selected {
background-color: rgba(13, 110, 253, 0.1);
}
/* Чекбоксы */
.form-check-input {
width: 1.2em;
height: 1.2em;
cursor: pointer;
}
.form-check-input:checked {
background-color: #0d6efd;
border-color: #0d6efd;
}
/* Бейджи */
.badge {
font-weight: 500;
padding: 0.35em 0.65em;
}
.badge.bg-pink {
background-color: #e83e8c !important;
color: white;
}
.specialty-badges {
max-width: 200px;
}
/* Свитч */
.form-switch .form-check-input {
width: 3em;
height: 1.5em;
}
/* Поля ввода */
.form-control:focus,
.form-select:focus {
border-color: #86b7fe;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
/* Кнопки */
.btn {
border-radius: 6px;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-lg {
padding: 0.75rem 1.5rem;
font-size: 1.1rem;
}
/* Уведомления */
.alert-fixed {
position: fixed;
top: 80px;
right: 20px;
z-index: 1050;
min-width: 300px;
max-width: 400px;
}
.alert {
border-radius: 8px;
border: none;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
/* Анимации */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.3s ease-in;
}
/* Пользовательские стили */
.user-row {
cursor: pointer;
}
.user-row:hover td {
background-color: rgba(13, 110, 253, 0.05);
}
/* Статус планировщика */
#schedulerStatus .badge {
font-size: 0.85rem;
padding: 0.4em 0.8em;
}
/* Адаптивность */
@media (max-width: 768px) {
.table-responsive {
font-size: 0.9rem;
}
.specialty-badges .badge {
font-size: 0.7rem;
padding: 0.25em 0.5em;
margin-bottom: 0.25rem;
}
.btn {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
}
/* Пустая таблица */
.text-center.py-5 {
color: #6c757d;
}
.text-center.py-5 .display-1 {
font-size: 4rem;
margin-bottom: 1rem;
}
+6 -6
View File
@@ -54,7 +54,7 @@ async function saveServerUrl() {
}
try {
const response = await fetch('/settings/medods', {
const response = await fetch('/api/medods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: serverUrl })
@@ -107,7 +107,7 @@ async function uploadApiKey() {
apiKey[headers[i]] = keyInfo[i];
}
const response = await fetch('/settings/medods', {
const response = await fetch('/api/medods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey })
@@ -132,7 +132,7 @@ async function uploadApiKey() {
// Загрузка списка запросов
async function loadRequests() {
try {
const response = await fetch('/settings/requests');
const response = await fetch('/api/requests');
const data = await response.json();
requestsData = data.requests ? data.requests : [];
renderRequestsList();
@@ -392,7 +392,7 @@ async function saveRequest() {
if (id) requestData.id = parseInt(id);
try {
const response = await fetch('/settings/requests', {
const response = await fetch('/api/requests', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
@@ -417,7 +417,7 @@ async function deleteRequest(id) {
if (!confirm('Вы уверены, что хотите удалить этот запрос?')) return;
try {
const response = await fetch(`/settings/requests/${id}`, {
const response = await fetch(`/api/requests/${id}`, {
method: 'DELETE'
});
@@ -448,7 +448,7 @@ async function executeCurrentRequest() {
async function executeRequest(id) {
try {
showLoader(true);
const response = await fetch('/settings/requests', {
const response = await fetch('/api/requests', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: parseInt(id) })
+362
View File
@@ -0,0 +1,362 @@
// Глобальные переменные
let originalSettings = null;
let hasChanges = false;
// Инициализация при загрузке страницы
document.addEventListener('DOMContentLoaded', function () {
// Сохраняем оригинальные настройки для сравнения
saveOriginalSettings();
// Отслеживаем изменения в форме
setupChangeListeners();
// Обновляем статус планировщика
updateSchedulerStatus();
});
// Сохранение оригинальных настроек
function saveOriginalSettings() {
originalSettings = {
selected_users: getSelectedUsers(),
static_text: document.getElementById('static_text').value,
full_name: document.getElementById('full_name').checked,
start_hour: parseInt(document.getElementById('start_hour').value),
end_hour: parseInt(document.getElementById('end_hour').value),
interval_minutes: parseInt(document.getElementById('interval_minutes').value),
enabled: document.getElementById('scheduler_enabled').checked
};
}
// Настройка отслеживания изменений
function setupChangeListeners() {
// Чекбоксы пользователей
document.querySelectorAll('.user-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function () {
updateSelectAllCheckbox();
checkForChanges();
});
});
// Поля формы
const formFields = ['static_text', 'start_hour', 'end_hour', 'interval_minutes'];
formFields.forEach(field => {
const element = document.getElementById(field);
if (element) {
element.addEventListener('input', checkForChanges);
}
});
// Свитчи
document.getElementById('full_name').addEventListener('change', checkForChanges);
document.getElementById('scheduler_enabled').addEventListener('change', checkForChanges);
document.getElementById('selectAll').addEventListener('change', checkForChanges);
}
// Проверка на изменения
function checkForChanges() {
const currentSettings = {
selected_users: getSelectedUsers(),
static_text: document.getElementById('static_text').value,
full_name: document.getElementById('full_name').checked,
start_hour: parseInt(document.getElementById('start_hour').value),
end_hour: parseInt(document.getElementById('end_hour').value),
interval_minutes: parseInt(document.getElementById('interval_minutes').value),
enabled: document.getElementById('scheduler_enabled').checked
};
hasChanges = JSON.stringify(originalSettings) !== JSON.stringify(currentSettings);
// Можно добавить визуальное отображение изменений
const saveButton = document.querySelector('.btn-success');
if (hasChanges && saveButton) {
saveButton.innerHTML = '<i class="bi bi-save me-2"></i>Сохранить изменения';
saveButton.classList.add('btn-warning');
saveButton.classList.remove('btn-success');
} else if (saveButton) {
saveButton.innerHTML = '<i class="bi bi-save me-2"></i>Сохранить все настройки';
saveButton.classList.remove('btn-warning');
saveButton.classList.add('btn-success');
}
}
// Получение выбранных пользователей
function getSelectedUsers() {
const selectedUsers = [];
document.querySelectorAll('.user-checkbox:checked').forEach(checkbox => {
const userId = parseInt(checkbox.id.replace('user_', ''));
selectedUsers.push(userId);
});
return selectedUsers;
}
// Обновление чекбокса "Выбрать все"
function updateSelectAllCheckbox() {
const allCheckboxes = document.querySelectorAll('.user-checkbox');
const checkedCheckboxes = document.querySelectorAll('.user-checkbox:checked');
const selectAllCheckbox = document.getElementById('selectAll');
if (allCheckboxes.length === checkedCheckboxes.length) {
selectAllCheckbox.checked = true;
selectAllCheckbox.indeterminate = false;
} else if (checkedCheckboxes.length === 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
} else {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = true;
}
}
// Выбрать всех пользователей
function selectAllUsers() {
document.querySelectorAll('.user-checkbox').forEach(checkbox => {
checkbox.checked = true;
});
updateSelectAllCheckbox();
checkForChanges();
}
// Снять выбор со всех пользователей
function deselectAllUsers() {
document.querySelectorAll('.user-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
updateSelectAllCheckbox();
checkForChanges();
}
// Переключить выбор всех пользователей
function toggleAllUsers() {
const selectAllCheckbox = document.getElementById('selectAll');
const isChecked = selectAllCheckbox.checked;
document.querySelectorAll('.user-checkbox').forEach(checkbox => {
checkbox.checked = isChecked;
});
selectAllCheckbox.indeterminate = false;
checkForChanges();
}
// Обновление списка пользователей
async function updateUsersList() {
try {
const response = await fetch('/api/posts?action=update_users');
const data = await response.json();
if (data.ok) {
showAlert('success', 'Список пользователей обновлен!');
// Перезагружаем страницу через 1.5 секунды
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
showAlert('danger', 'Ошибка обновления списка пользователей');
}
} catch (error) {
console.error('Ошибка:', error);
showAlert('danger', 'Ошибка обновления списка пользователей');
}
}
// Публикация сейчас
async function publishNow() {
const selectedUsers = getSelectedUsers();
const staticText = document.getElementById('static_text').value.trim();
if (selectedUsers.length === 0) {
showAlert('warning', 'Выберите хотя бы одного сотрудника для публикации');
return;
}
if (!staticText) {
showAlert('warning', 'Введите текст поста');
return;
}
try {
const response = await fetch('/api/posts?action=handle_posts');
const data = await response.json();
if (data.ok) {
showAlert('success', 'Публикация запущена! Проверьте ваше сообщество VK.');
} else {
showAlert('danger', 'Ошибка при запуске публикации');
}
} catch (error) {
console.error('Ошибка:', error);
showAlert('danger', 'Ошибка при запуске публикации');
}
}
// Сохранение настроек
async function saveSettings() {
const selectedUsers = getSelectedUsers();
const staticText = document.getElementById('static_text').value.trim();
const fullName = document.getElementById('full_name').checked;
const startHour = parseInt(document.getElementById('start_hour').value);
const endHour = parseInt(document.getElementById('end_hour').value);
const intervalMinutes = parseInt(document.getElementById('interval_minutes').value);
const schedulerEnabled = document.getElementById('scheduler_enabled').checked;
// Валидация
if (selectedUsers.length === 0 && hasChanges) {
showAlert('warning', 'Выберите хотя бы одного сотрудника для публикации');
return;
}
if (!staticText && hasChanges) {
showAlert('warning', 'Введите текст поста');
return;
}
if (startHour < 0 || startHour > 23) {
showAlert('warning', 'Время начала должно быть от 0 до 23 часов');
return;
}
if (endHour < 0 || endHour > 23) {
showAlert('warning', 'Время окончания должно быть от 0 до 23 часов');
return;
}
if (startHour >= endHour) {
showAlert('warning', 'Время начала должно быть раньше времени окончания');
return;
}
if (intervalMinutes < 1 || intervalMinutes > 1440) {
showAlert('warning', 'Интервал должен быть от 1 до 1440 минут');
return;
}
// Подготовка данных для отправки
const postData = {
vkPostData: {},
schedulerData: {}
};
// Только измененные данные для vkPostData
if (JSON.stringify(selectedUsers) !== JSON.stringify(originalSettings.selected_users) ||
staticText !== originalSettings.static_text ||
fullName !== originalSettings.full_name) {
postData.vkPostData = {
selectedUsers: selectedUsers,
static_text: staticText,
full_name: fullName
};
}
// Только измененные данные для schedulerData
if (startHour !== originalSettings.start_hour ||
endHour !== originalSettings.end_hour ||
intervalMinutes !== originalSettings.interval_minutes ||
schedulerEnabled !== originalSettings.enabled) {
postData.schedulerData = {
startTime: startHour.toString(),
endTime: endHour.toString(),
interval_minutes: intervalMinutes.toString(),
enabled: schedulerEnabled
};
}
// Если нет изменений
if (Object.keys(postData.vkPostData).length === 0 &&
Object.keys(postData.schedulerData).length === 0) {
showAlert('info', 'Нет изменений для сохранения');
return;
}
try {
const response = await fetch('/api/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
});
const data = await response.json();
if (response.ok && data.status === 'ok') {
showAlert('success', 'Настройки успешно сохранены!');
// Обновляем оригинальные настройки
saveOriginalSettings();
checkForChanges();
// Если менялись настройки планировщика, обновляем статус
if (Object.keys(postData.schedulerData).length > 0) {
setTimeout(updateSchedulerStatus, 1000);
}
} else {
const error = data.message || 'Ошибка сохранения';
showAlert('danger', error);
}
} catch (error) {
console.error('Ошибка:', error);
showAlert('danger', 'Ошибка сохранения настроек');
}
}
// Обновление статуса планировщика
async function updateSchedulerStatus() {
try {
// Здесь можно добавить запрос для получения актуального статуса
// Пока просто обновляем визуально
const enabled = document.getElementById('scheduler_enabled').checked;
const statusBadge = document.getElementById('schedulerStatus');
if (statusBadge) {
if (enabled) {
statusBadge.innerHTML = '<span class="badge bg-success"><i class="bi bi-play-circle me-1"></i>Активен</span>';
} else {
statusBadge.innerHTML = '<span class="badge bg-secondary"><i class="bi bi-stop-circle me-1"></i>Неактивен</span>';
}
}
} catch (error) {
console.error('Ошибка обновления статуса:', error);
}
}
// Вспомогательные функции для уведомлений
function showAlert(type, message) {
const alertContainer = document.getElementById('alertContainer');
// Очищаем старые алерты
alertContainer.innerHTML = '';
// Создаем новый алерт
const alert = document.createElement('div');
alert.className = `alert alert-${type} alert-dismissible fade show shadow`;
// Иконка для типа алерта
const icon = getAlertIcon(type);
alert.innerHTML = `
<div class="d-flex align-items-center">
<i class="bi ${icon} me-2 fs-5"></i>
<div class="flex-grow-1">${message}</div>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
alertContainer.appendChild(alert);
// Автоматическое удаление через 5 секунд
setTimeout(() => {
if (alert.parentNode) {
alert.classList.remove('show');
setTimeout(() => alert.remove(), 150);
}
}, 5000);
}
function getAlertIcon(type) {
const icons = {
'success': 'bi-check-circle-fill',
'warning': 'bi-exclamation-triangle-fill',
'danger': 'bi-x-circle-fill',
'info': 'bi-info-circle-fill'
};
return icons[type] || 'bi-info-circle-fill';
}
+1 -1
View File
@@ -72,7 +72,7 @@ async function saveVkSettings() {
}
try {
const response = await fetch('/settings/vk', {
const response = await fetch('/api/vk', {
method: 'POST',
headers: {
'Content-Type': 'application/json'