This commit is contained in:
2026-05-07 18:05:17 +03:00
parent 59e283945c
commit 1a5b10b129

View File

@@ -24,6 +24,11 @@
Импорт JSON
<input type="file" accept=".json" @change="importConfigFromJson" class="hidden">
</label>
<button @click="openExitModal" class="btn-danger flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
@@ -494,6 +499,30 @@
</div>
</div>
</Transition>
<!-- Модалка подтверждения выхода без сохранения -->
<Transition name="fade">
<div v-if="exitConfirmModal.show" class="fixed inset-0 z-50 overflow-y-auto" @click.self="exitConfirmModal.show = false">
<div class="fixed inset-0 bg-black/50 backdrop-blur-sm"></div>
<div class="flex items-center justify-center min-h-screen p-4">
<div class="relative bg-white rounded-2xl shadow-xl max-w-md w-full">
<div class="p-6 text-center">
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-amber-100 mb-4">
<svg class="h-6 w-6 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h3 class="text-lg font-medium text-gray-900 mb-2">Несохранённые изменения</h3>
<p class="text-sm text-gray-500 mb-6">Вы уверены, что хотите выйти? Все несохранённые данные будут потеряны.</p>
<div class="flex justify-center space-x-3">
<button @click="exitConfirmModal.show = false" class="btn-secondary">Остаться</button>
<button @click="confirmExit" class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700">Выйти</button>
</div>
</div>
</div>
</div>
</div>
</Transition>
</AppLayout>
</template>
@@ -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<number | null>(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 // <- Страница полностью загружена
})
</script>
@@ -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;
}
</style>