1919 </div >
2020
2121 <div v-if =" currentTab === 'summary'" class =" tab-content" >
22+
23+ <div class =" mass-operations-section" >
24+ <div class =" ops-card" >
25+ <div class =" ops-info" >
26+ <h3 >Массовые операции с данными</h3 >
27+ <p >Загрузите JSON-файл для восстановления базы или экспортируйте текущее состояние всех сущностей (Пользователи, Комнаты, ПК, Бронирования).</p >
28+ </div >
29+
30+ <div class =" ops-actions" >
31+ <div class =" import-zone" @click =" $refs.fileInput.click()" >
32+ <input
33+ type =" file"
34+ ref =" fileInput"
35+ style =" display : none "
36+ accept =" .json"
37+ @change =" handleFileImport"
38+ />
39+ <div class =" import-placeholder" >
40+ <span >Нажмите, чтобы выбрать <strong >.json</strong ></span >
41+ </div >
42+ </div >
43+
44+ <div class =" export-zone" >
45+ <button class =" btn-export-all" @click =" handleExport" >
46+ Экспортировать всё в JSON
47+ </button >
48+ </div >
49+ </div >
50+ </div >
51+ </div >
52+
2253 <div class =" kpi-grid" >
2354 <div class =" kpi-card" v-for =" stat in kpiStats" :key =" stat.label" >
2455 <span class =" kpi-label" >{{ stat.label }}</span >
6293
6394<script setup>
6495import { ref , onMounted } from ' vue'
96+ import { useAuthStore } from ' @/stores/auth'
6597import AdminLogs from ' ./AdminLogs.vue'
6698
99+ const authStore = useAuthStore ()
67100const currentTab = ref (' summary' )
68- const tabNames = { summary: ' Сводка' , logs: ' Журнал' , schedule : ' Расписание ' }
101+ const tabNames = { summary: ' Сводка' , logs: ' Журнал' }
69102
70103const kpiStats = ref ([
71104 { label: ' В сети' , value: ' 342' , color: ' #10B981' },
@@ -74,11 +107,84 @@ const kpiStats = ref([
74107 { label: ' Неявки' , value: ' 5.2%' , color: ' #F59E0B' }
75108])
76109
110+ const handleExport = async () => {
111+ try {
112+ const response = await fetch (' http://localhost:3000/api/admin/export-all' , {
113+ headers: { ' Authorization' : ` Bearer ${ authStore .token } ` }
114+ });
115+
116+ if (! response .ok ) throw new Error (' Ошибка сервера' );
117+
118+ const data = await response .json ();
119+ const blob = new Blob ([JSON .stringify (data, null , 2 )], { type: ' application/json' });
120+ const url = window .URL .createObjectURL (blob);
121+
122+ const link = document .createElement (' a' );
123+ link .href = url;
124+ link .setAttribute (' download' , ` backup_${ new Date ().toLocaleDateString ()} .json` );
125+ document .body .appendChild (link);
126+ link .click ();
127+ link .remove ();
128+ } catch (err) {
129+ alert (' Не удалось экспортировать данные: ' + err .message );
130+ }
131+ }
132+
133+ const handleFileImport = (event ) => {
134+ const file = event .target .files [0 ];
135+ if (! file) return ;
136+
137+ const reader = new FileReader ();
138+ reader .onload = async (e ) => {
139+ try {
140+ let jsonData;
141+
142+ try {
143+ jsonData = JSON .parse (e .target .result );
144+ } catch (parseErr) {
145+ throw new Error (" Файл не является корректным JSON-документом или поврежден." );
146+ }
147+
148+ const hasAnyCollection = [' Users' , ' Rooms' , ' Computers' , ' Bookings' ].some (key => key in jsonData);
149+ if (! hasAnyCollection) {
150+ throw new Error (" В файле не найдено ни одной известной коллекции данных." );
151+ }
152+
153+ if (! confirm (" ВНИМАНИЕ: Это действие полностью перезапишет базу данных. Вы уверены?" )) {
154+ event .target .value = ' ' ;
155+ return ;
156+ }
157+
158+ const response = await fetch (' http://localhost:3000/api/admin/import-all' , {
159+ method: ' POST' ,
160+ headers: {
161+ ' Content-Type' : ' application/json' ,
162+ ' Authorization' : ` Bearer ${ authStore .token } `
163+ },
164+ body: JSON .stringify (jsonData)
165+ });
166+
167+ const result = await response .json ();
168+
169+ if (response .ok ) {
170+ alert (" Успех: " + result .message );
171+ window .location .reload ();
172+ } else {
173+ throw new Error (result .message || ' Ошибка при импорте' );
174+ }
175+ } catch (err) {
176+ alert (" Ошибка валидации: " + err .message );
177+ } finally {
178+ event .target .value = ' ' ;
179+ }
180+ };
181+ reader .readAsText (file);
182+ }
183+
77184onMounted (async () => {
78- // TODO: запрос к бекенду за реальными KPI
79185})
80186 </script >
81187
82188<style lang="scss" scoped>
83189@use " @/assets/scss/pages/admin-dashboard" ;
84- </style >
190+ </style >
0 commit comments