From 1a5b10b1293af792dc0ea1cfd0f79254f5f1e169 Mon Sep 17 00:00:00 2001 From: Danil-Bodry Date: Thu, 7 May 2026 18:05:17 +0300 Subject: [PATCH] up --- frontend/src/views/OlapConstructor.vue | 102 ++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 10 deletions(-) diff --git a/frontend/src/views/OlapConstructor.vue b/frontend/src/views/OlapConstructor.vue index 486527a..df9471c 100644 --- a/frontend/src/views/OlapConstructor.vue +++ b/frontend/src/views/OlapConstructor.vue @@ -24,6 +24,11 @@ Импорт JSON + @@ -494,6 +499,30 @@ + + + +
+
+
+
+
+
+ + + +
+

Несохранённые изменения

+

Вы уверены, что хотите выйти? Все несохранённые данные будут потеряны.

+
+ + +
+
+
+
+
+
@@ -585,7 +614,9 @@ const active = ref(false) const searchQuery = ref('') const activeTab = ref<'table' | 'sql'>('table') const collapsed = ref({ number: false, category: false, filter: false }) - +const isLoadingQuery = ref(false) +const isDirty = ref(false) +const isReady = ref(false) // Поля для сохранения запроса const queryId = ref(null) const queryName = ref('') @@ -615,8 +646,13 @@ let dragReorderType: string | null = null let dragReorderFromIdx: number | null = null const resetModal = ref({ show: false }) +const exitConfirmModal = ref({ show: false }) // ---------- Вспомогательные функции ---------- +function markDirty() { + isDirty.value = true +} + const buildAvailableFields = (): AvailableField[] => { if (!columnsData.value.length) return [] const selected = reportType.value @@ -757,9 +793,29 @@ function triggerSqlUpdate() { } // Наблюдение за изменениями для перегенерации SQL -watch([rowFields, columnFields, valueFields, filterFields, reportType, buildSummary, dateTo, daysBack, tableName, selectedDbConnection], () => { - triggerSqlUpdate() -}, { deep: true }) +watch( + [ + rowFields, + columnFields, + valueFields, + filterFields, + reportType, + buildSummary, + dateTo, + daysBack, + tableName, + active, + queryName, + selectedRestaurants, + selectedDbConnection + ], + () => { + if (isReady.value && !isLoadingQuery.value) { + isDirty.value = true + } + }, + { deep: true } +) // ---------- Экспорт / импорт JSON (iiko‑конфиг) ---------- async function exportConfigAsJson() { @@ -855,6 +911,7 @@ function importConfigFromJson(event: Event) { } }) } + isDirty.value = true showNotification('Конфигурация iiko загружена', 'success') } catch (err: any) { showNotification('Ошибка при загрузке JSON: ' + err.message, 'error') @@ -866,6 +923,7 @@ function importConfigFromJson(event: Event) { // ---------- Работа с сохранёнными запросами (CRUD через API) ---------- async function loadQuery(id: number) { + isLoadingQuery.value = true try { const res = await fetch(`/api/olap/queries/${id}`) if (!res.ok) throw new Error() @@ -873,8 +931,7 @@ async function loadQuery(id: number) { // 1. Базовые поля запроса queryName.value = data.name - - active.value = data.active ?? true; + active.value = data.active ?? true // 2. Подключение к БД if (data.dbConnectionId) { @@ -931,7 +988,7 @@ async function loadQuery(id: number) { valueFields.value.push({ ...(field as NumberField), id: Date.now() + Math.random().toString(), - aggregation: 'sum' // по умолчанию, можно расширить если в конфиге будет + aggregation: 'sum' }) } } @@ -956,10 +1013,12 @@ async function loadQuery(id: number) { } } - // 10. Обновляем SQL (вызовется автоматически через watch) triggerSqlUpdate() + isDirty.value = false } catch (e: any) { showNotification('Ошибка загрузки запроса: ' + e.message, 'error') + } finally { + isLoadingQuery.value = false } } @@ -998,7 +1057,7 @@ async function saveQuery() { }) if (!res.ok) throw new Error(await res.text()) showNotification('Запрос сохранён', 'success') - router.push('/olap/queries') + isDirty.value = false } catch (e: any) { showNotification('Ошибка сохранения: ' + e.message, 'error') } @@ -1209,6 +1268,7 @@ const confirmReset = () => { selectedRestaurants.value = [] selectedDbConnection.value = null sqlScript.value = '-- Выберите подключение к БД для генерации SQL' + isDirty.value = true showNotification('Все настройки сброшены', 'success') } const toggleSection = (section: 'number' | 'category' | 'filter') => { @@ -1265,17 +1325,36 @@ const filteredDbConnectionsList = computed(() => { return dbConnectionsList.value.filter(c => c.name.toLowerCase().includes(lower)) }) +function openExitModal() { + if (isDirty.value) { + exitConfirmModal.value.show = true + } else { + router.push('/olap/queries') + } +} + +function confirmExit() { + exitConfirmModal.value.show = false + router.push('/olap/queries') +} + watch(reportType, () => { - refreshFieldsAndReset() + if (isReady.value) refreshFieldsAndReset() }) onMounted(async () => { + isLoadingQuery.value = true await fetchColumns() + isLoadingQuery.value = false + if (route.params.id) { queryId.value = parseInt(route.params.id as string) await loadQuery(queryId.value) + } else { + isDirty.value = false } triggerSqlUpdate() + isReady.value = true // <- Страница полностью загружена }) @@ -1299,4 +1378,7 @@ onMounted(async () => { background: #cbd5e1; border-radius: 4px; } +.btn-danger { + @apply bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 transition; +}