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