Files
2026-06-09 18:52:21 +02:00

240 lines
8.7 KiB
PHP

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Выгрузка пользователей</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f2f5;
color: #333;
}
#app {
width: 100%;
max-width: 500px;
padding: 40px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
text-align: center;
}
h1 {
font-size: 24px;
margin-top: 0;
margin-bottom: 20px;
}
.user-count {
font-size: 16px;
margin-bottom: 25px;
color: #555;
}
.export-button {
background-color: #007bff;
color: white;
border: none;
padding: 15px 30px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
margin-bottom: 20px;
}
.export-button:hover:not(:disabled) {
background-color: #0056b3;
}
.export-button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.download-link {
font-size: 16px;
margin-bottom: 25px;
min-height: 24px;
}
.download-link a {
color: #28a745;
text-decoration: none;
font-weight: bold;
}
.download-link a:hover {
text-decoration: underline;
}
.status {
font-size: 14px;
color: #6c757d;
}
</style>
</head>
<body>
<div id="app">
<h1>Выгрузка пользователей</h1>
<div class="user-count">
Сейчас пользователей в БД: <strong>@{{ userCount }}</strong>
</div>
<button class="export-button" @click="startExport" :disabled="isExporting">
Выгрузить пользователей
</button>
<div class="download-link">
<a v-if="downloadUrl" :href="downloadUrl" download>Скачать в формате .csv</a>
</div>
<div class="status">
Статус: <strong>@{{ statusMessage }}</strong>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp, ref, onMounted } = Vue;
createApp({
setup() {
const userCount = ref('загрузка...');
const statusMessage = ref('ожидание');
const isExporting = ref(false);
const downloadUrl = ref(null);
let pollInterval = null;
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const fetchUserCount = async () => {
try {
const response = await fetch('/users-export/counter');
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
userCount.value = data.count.toLocaleString('ru-RU');
} catch (error) {
console.error('Ошибка при получении количества пользователей:', error);
userCount.value = 'ошибка';
}
};
const startExport = async () => {
try {
isExporting.value = true;
statusMessage.value = 'Отправка запроса...';
downloadUrl.value = null;
const response = await fetch('/users-export/export', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken
},
body: JSON.stringify({})
});
if (!response.ok) {
if (response.status === 419) {
statusMessage.value = 'Ошибка: Сессия истекла. Пожалуйста, обновите страницу.';
} else {
throw new Error('Network response was not ok: ' + response.statusText);
}
isExporting.value = false;
return;
}
const data = await response.json();
if (data.export_status === 'progress') {
statusMessage.value = 'Выгрузка уже идет. Пока нельзя начать новую выгрузку';
startPolling();
} else if (data.export_status === 'started') {
statusMessage.value = 'Выгрузка начата.';
startPolling();
} else {
isExporting.value = false;
statusMessage.value = 'Неизвестный статус: ' + data.export_status;
}
} catch (error) {
console.error('Ошибка при запуске экспорта:', error);
statusMessage.value = 'Ошибка при запуске выгрузки';
isExporting.value = false;
}
};
const startPolling = () => {
if (pollInterval) return;
pollInterval = setInterval(async () => {
try {
const response = await fetch('/users-export/status');
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
if (data.export_status === 'standby') {
stopPolling();
statusMessage.value = 'выгрузка завершена';
isExporting.value = false;
if (data.last_export_file) {
downloadUrl.value = '/' + data.last_export_file;
}
} else if (data.export_status === 'failed') {
stopPolling();
statusMessage.value = 'Ошибка выгрузки (failed)';
isExporting.value = false;
}
} catch (error) {
console.error('Ошибка при опросе статуса:', error);
stopPolling();
statusMessage.value = 'Ошибка связи с сервером при опросе статуса';
isExporting.value = false;
}
}, 6000); // 6 сек
};
const stopPolling = () => {
if (pollInterval) {
clearInterval(pollInterval);
pollInterval = null;
}
};
onMounted(() => {
fetchUserCount();
fetch('/users-export/status')
.then(res => res.json())
.then(data => {
if (data.export_status === 'progress') {
isExporting.value = true;
statusMessage.value = 'Выгрузка в процессе...';
startPolling();
} else if (data.export_status === 'standby' && data.last_export_file) {
statusMessage.value = 'Готов файл с прошлой выгрузки';
downloadUrl.value = '/' + data.last_export_file;
}
})
.catch(err => console.error(err));
});
return {
userCount,
statusMessage,
isExporting,
downloadUrl,
startExport
};
},
compilerOptions: {
delimiters: ['@{{', '}}']
}
}).mount('#app');
</script>
</body>
</html>