v1.1
This commit is contained in:
		
							
								
								
									
										5
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								.env
									
									
									
									
									
								
							| @@ -1 +1,4 @@ | ||||
| VITE_API_BASE_URL=https://45.146.164.63:8080 | ||||
| # VUE_APP_API_URL=http://31.129.106.67:8080 | ||||
| # VUE_APP_GEO_URL=http://31.129.106.67:6001 | ||||
| VUE_APP_API_URL=http://127.0.0.1:8080 | ||||
| VUE_APP_GEO_URL=http://127.0.0.1:6001 | ||||
| @@ -1,8 +1,8 @@ | ||||
| <template> | ||||
|   <Main /> | ||||
|   <StopInfo /> | ||||
|   <CarrierInfo /> | ||||
|   <WeatherInfo /> | ||||
|   <CarrierInfo @president-appeal-toggle="presidentOpen = $event" /> | ||||
|   <WeatherInfo :is-president-address-open="presidentOpen" /> | ||||
|   <RouteInfo /> | ||||
| </template> | ||||
|  | ||||
| @@ -22,6 +22,11 @@ export default { | ||||
|     WeatherInfo, | ||||
|     RouteInfo, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       presidentOpen: false, | ||||
|     }; | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -18,18 +18,29 @@ body { | ||||
|   user-select: none; | ||||
| } | ||||
|  | ||||
| .station-label-no-bg { | ||||
| .station-label-no-bg, | ||||
| .station-name-ru { | ||||
|   background: transparent !important; | ||||
|   border: none !important; | ||||
|   box-shadow: none !important; | ||||
|   font-size: 16px; | ||||
|   font-weight: 600; | ||||
|   color: #fff; | ||||
|  | ||||
|   /* max-width: 200px; */ | ||||
|   padding: 0 !important; | ||||
|   /* text-wrap: revert; */ | ||||
| } | ||||
|  | ||||
| .station-name-ru { | ||||
|   font-size: 16px; | ||||
|   font-weight: 600; | ||||
|   color: #fff; | ||||
| } | ||||
|  | ||||
| .station-name-en { | ||||
|   font-size: 14px; | ||||
|   color: #ffffffad; | ||||
|   text-align: left; | ||||
| } | ||||
|  | ||||
| .station-label-no-bg:before { | ||||
|   display: none !important; | ||||
| } | ||||
| @@ -40,6 +51,7 @@ body { | ||||
|   top: 0; | ||||
|   right: 0; | ||||
|   height: 100%; | ||||
|   pointer-events: none; | ||||
| } | ||||
|  | ||||
| .stopinfo .bg { | ||||
| @@ -48,6 +60,15 @@ body { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: space-between; | ||||
|   pointer-events: none; | ||||
| } | ||||
|  | ||||
| .stopinfo .bg * { | ||||
|   pointer-events: auto; | ||||
| } | ||||
|  | ||||
| .stopinfo .container { | ||||
|   margin-bottom: 75px !important; | ||||
| } | ||||
|  | ||||
| .container { | ||||
| @@ -118,14 +139,22 @@ body { | ||||
|   top: 18px; | ||||
| } | ||||
|  | ||||
| .landmarks-container { | ||||
|   position: absolute; | ||||
|   bottom: 0; | ||||
| } | ||||
|  | ||||
| .stoparticles { | ||||
|   padding: 0 15px; | ||||
|   height: 50px; | ||||
|   color: #fff; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   margin-top: auto; | ||||
|   gap: 45px; | ||||
|   text-align: center; | ||||
|   gap: 15px; | ||||
|   justify-content: space-around; | ||||
|   background: rgb(187, 179, 170); | ||||
|   background: linear-gradient( | ||||
|     180deg, | ||||
| @@ -150,6 +179,7 @@ body { | ||||
|   font-weight: 600; | ||||
|   cursor: pointer; | ||||
|   margin: 0 15px 15px 15px; | ||||
|   pointer-events: auto; | ||||
| } | ||||
|  | ||||
| .stop-buttons-container { | ||||
| @@ -167,6 +197,7 @@ body { | ||||
|   cursor: pointer; | ||||
|   width: 100%; | ||||
|   margin-top: 15px; | ||||
|   pointer-events: auto; | ||||
| } | ||||
|  | ||||
| .white { | ||||
| @@ -225,6 +256,7 @@ body { | ||||
|   left: 0; | ||||
|   height: 100%; | ||||
|   width: 350px; | ||||
|   pointer-events: none; | ||||
| } | ||||
|  | ||||
| .carrierinfo .bg { | ||||
| @@ -310,7 +342,7 @@ body { | ||||
| .weatherinfo { | ||||
|   z-index: 450; | ||||
|   width: 225px; | ||||
|   padding: 10px 15px; | ||||
|   padding: 10px 10px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
| @@ -336,8 +368,9 @@ body { | ||||
|  | ||||
| .weatherinfo .date { | ||||
|   text-align: center; | ||||
|   font-size: 18px; | ||||
|   padding: 2px 0 8px 0; | ||||
|   font-size: 15px; | ||||
|   font-weight: lighter; | ||||
|   padding: 0 0 8px 0; | ||||
|   width: 100%; | ||||
|   border-bottom: 1px solid #ffffff7a; | ||||
| } | ||||
| @@ -346,13 +379,16 @@ body { | ||||
|   width: 75px; | ||||
| } | ||||
|  | ||||
| .current-weather { | ||||
|   padding-left: 10px; | ||||
| } | ||||
|  | ||||
| .current-weather div { | ||||
|   text-align: center; | ||||
|   margin-top: 5px; | ||||
| } | ||||
|  | ||||
| .temperature-celsius { | ||||
|   font-size: 35px; | ||||
|   font-size: 50px; | ||||
| } | ||||
|  | ||||
| .forecast-day { | ||||
| @@ -366,7 +402,7 @@ body { | ||||
|  | ||||
| .weather-forecast { | ||||
|   width: 100%; | ||||
|   padding: 0 10px; | ||||
|   padding: 0; | ||||
|   margin-top: 10px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| @@ -376,15 +412,22 @@ body { | ||||
| .forecast { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 7px; | ||||
|   gap: 10px; | ||||
|   width: 90px; | ||||
| } | ||||
|  | ||||
| .forecast-day { | ||||
|   font-size: 17px; | ||||
| } | ||||
|  | ||||
| .additional-forecast { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   border-top: 1px solid #ffffff7a; | ||||
|   padding: 10px 0 0 7px; | ||||
|   padding: 10px 0 0 3px; | ||||
|   gap: 7px; | ||||
|   font-size: 18px; | ||||
|   font-weight: bold; | ||||
| } | ||||
|  | ||||
| .additional-forecast .humidity, | ||||
| @@ -417,7 +460,7 @@ body { | ||||
| .route-names { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 15px; | ||||
|   /* gap: 2px; */ | ||||
|   padding: 0 10px; | ||||
| } | ||||
|  | ||||
| @@ -478,7 +521,7 @@ body { | ||||
|   position: absolute; | ||||
|   bottom: 10px; | ||||
|   left: 310px; | ||||
|   background: rgba(0, 0, 0, 0.4); | ||||
|   background: rgba(0, 0, 0, 0); | ||||
|   border: none; | ||||
|   color: #fff; | ||||
|   font-size: 24px; | ||||
| @@ -487,6 +530,7 @@ body { | ||||
|   border-radius: 5px; | ||||
|   z-index: 1001; | ||||
|   transition: left 0.3s ease; | ||||
|   pointer-events: auto; | ||||
| } | ||||
|  | ||||
| .carrierinfo .bg.hidden + .carrier-toggle { | ||||
| @@ -550,21 +594,6 @@ body { | ||||
|   overflow-y: auto; | ||||
| } | ||||
|  | ||||
| .carrier-toggle { | ||||
|   position: absolute; | ||||
|   bottom: 10px; | ||||
|   left: 310px; | ||||
|   background: rgba(0, 0, 0, 0.4); | ||||
|   border: none; | ||||
|   color: #fff; | ||||
|   font-size: 24px; | ||||
|   cursor: pointer; | ||||
|   padding: 5px 10px; | ||||
|   border-radius: 5px; | ||||
|   z-index: 1001; | ||||
|   transition: left 0.3s ease; | ||||
| } | ||||
|  | ||||
| .carrierinfo .bg.hidden + .carrier-toggle { | ||||
|   left: 10px; | ||||
| } | ||||
| @@ -576,6 +605,7 @@ body { | ||||
|  | ||||
| .bg { | ||||
|   transition: transform 0.3s ease; | ||||
|   pointer-events: auto; | ||||
| } | ||||
|  | ||||
| .dropdown-name { | ||||
| @@ -680,16 +710,6 @@ li.checked { | ||||
|  | ||||
| .stoparticle-option { | ||||
|   cursor: pointer; | ||||
|   margin-right: 10px; | ||||
| } | ||||
|  | ||||
| .stoparticle-option.selected { | ||||
|   text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .stoparticle-option { | ||||
|   cursor: pointer; | ||||
|   margin-right: 10px; | ||||
| } | ||||
|  | ||||
| .stoparticle-option.selected { | ||||
| @@ -958,3 +978,21 @@ li.checked { | ||||
| .sight-preview-wrapper { | ||||
|   max-height: 300px; | ||||
| } | ||||
|  | ||||
| .hidden { | ||||
|   display: none !important; | ||||
| } | ||||
|  | ||||
| .governor-appeal { | ||||
|   top: 140px; | ||||
|   width: 440px; | ||||
| } | ||||
|  | ||||
| .governor-appeal p { | ||||
|   max-height: 400px; | ||||
|   line-height: 22px; | ||||
| } | ||||
|  | ||||
| .governor-appeal h3 { | ||||
|   font-size: 22px; | ||||
| } | ||||
|   | ||||
| @@ -589,7 +589,6 @@ | ||||
|             clip-rule="evenodd" | ||||
|           /> | ||||
|         </svg> | ||||
|  | ||||
|         <span class="gos-name" | ||||
|           >При поддержке Правительства <br /> | ||||
|           Санкт-Петербурга</span | ||||
| @@ -713,7 +712,7 @@ | ||||
|       </svg> | ||||
|     </button> | ||||
|   </div> | ||||
|   <div v-if="showGovernorAppeal" class="sight-preview-panel"> | ||||
|   <div v-if="showGovernorAppeal" class="governor-appeal sight-preview-panel"> | ||||
|     <img :src="governorAppealImage" v-if="governorAppealImage" /> | ||||
|     <h3>{{ governorAppealTitle }}</h3> | ||||
|     <p>{{ governorAppealText }}</p> | ||||
| @@ -739,16 +738,17 @@ | ||||
|  | ||||
| <script> | ||||
| import "../assets/style/main.css"; | ||||
| import { API_URL, GEO_URL } from "../config"; | ||||
|  | ||||
| export default { | ||||
|   name: "StopInfo", | ||||
|   name: "CarrierInfo", | ||||
|   data() { | ||||
|     return { | ||||
|       stations: [], | ||||
|       showList: false, | ||||
|       sights: [], | ||||
|       showSightsList: false, | ||||
|       routeId: null, | ||||
|       routeId: 1, | ||||
|       isHidden: true, | ||||
|       selectedSightId: null, | ||||
|       selectedSightName: "", | ||||
| @@ -763,6 +763,7 @@ export default { | ||||
|       governorAppealId: null, | ||||
|       selectedSightWatermarkLU: "", | ||||
|       selectedSightWatermarkRD: "", | ||||
|       inactivityTimer: null, | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
| @@ -778,16 +779,10 @@ export default { | ||||
|     }, | ||||
|     async fetchStops() { | ||||
|       try { | ||||
|         const geoResponse = await fetch( | ||||
|           "http://31.129.106.67:6001/v1/geolocation/context" | ||||
|         ); | ||||
|         const geoResponse = await fetch(`${GEO_URL}/v1/geolocation/context`); | ||||
|         const geoData = await geoResponse.json(); | ||||
|         const token = this.getCookie("auth_token"); | ||||
|         const routeDetailsRes = await fetch( | ||||
|           `https://wn-ts.krbl.ru/route/${geoData.routeId}`, | ||||
|           { | ||||
|             headers: { Authorization: `Bearer ${token}` }, | ||||
|           } | ||||
|           `${API_URL}/route/${geoData.routeId}` | ||||
|         ); | ||||
|         const routeDetails = await routeDetailsRes.json(); | ||||
|         const appealId = routeDetails.governor_appeal; | ||||
| @@ -802,29 +797,18 @@ export default { | ||||
|  | ||||
|         if (appealId && appealId !== 0) { | ||||
|           try { | ||||
|             const articleRes = await fetch( | ||||
|               `https://wn-ts.krbl.ru/article/${appealId}`, | ||||
|               { | ||||
|                 headers: { Authorization: `Bearer ${token}` }, | ||||
|               } | ||||
|             ); | ||||
|             const articleRes = await fetch(`${API_URL}/article/${appealId}`); | ||||
|             const articleData = await articleRes.json(); | ||||
|             this.governorAppealTitle = articleData.heading; | ||||
|             this.governorAppealText = articleData.body; | ||||
|  | ||||
|             const mediaRes = await fetch( | ||||
|               `https://wn-ts.krbl.ru/article/${appealId}/media`, | ||||
|               { | ||||
|                 headers: { Authorization: `Bearer ${token}` }, | ||||
|               } | ||||
|               `${API_URL}/article/${appealId}/media` | ||||
|             ); | ||||
|             const mediaData = await mediaRes.json(); | ||||
|             if (mediaData.length) { | ||||
|               const imageRes = await fetch( | ||||
|                 `http://31.129.106.67:8080/media/${mediaData[0].id}/download`, | ||||
|                 { | ||||
|                   headers: { Authorization: `Bearer ${token}` }, | ||||
|                 } | ||||
|                 `${API_URL}/media/${mediaData[0].id}/download` | ||||
|               ); | ||||
|               this.governorAppealImage = await imageRes.url; | ||||
|             } else { | ||||
| @@ -846,10 +830,7 @@ export default { | ||||
|         if (!this.routeId) throw new Error("Route number not found"); | ||||
|  | ||||
|         const stopsResponse = await fetch( | ||||
|           `https://wn-ts.krbl.ru/route/${this.routeId}/station`, | ||||
|           { | ||||
|             headers: { Authorization: `Bearer ${token}` }, | ||||
|           } | ||||
|           `${API_URL}/route/${this.routeId}/station` | ||||
|         ); | ||||
|         const stopsData = await stopsResponse.json(); | ||||
|         this.stations = stopsData; | ||||
| @@ -859,22 +840,11 @@ export default { | ||||
|     }, | ||||
|     async fetchSights() { | ||||
|       try { | ||||
|         const token = this.getCookie("auth_token"); | ||||
|         const response = await fetch( | ||||
|           `https://wn-ts.krbl.ru/route/${this.routeId}/sight`, | ||||
|           { | ||||
|             headers: { Authorization: `Bearer ${token}` }, | ||||
|           } | ||||
|         ); | ||||
|         const response = await fetch(`${API_URL}/route/${this.routeId}/sight`); | ||||
|         const rawSights = await response.json(); | ||||
|         const detailedSights = await Promise.all( | ||||
|           rawSights.map(async (sight) => { | ||||
|             const detailRes = await fetch( | ||||
|               `https://wn-ts.krbl.ru/sight/${sight.id}`, | ||||
|               { | ||||
|                 headers: { Authorization: `Bearer ${token}` }, | ||||
|               } | ||||
|             ); | ||||
|             const detailRes = await fetch(`${API_URL}/sight/${sight.id}`); | ||||
|             const detail = await detailRes.json(); | ||||
|             return { id: sight.id, name: detail.name }; | ||||
|           }) | ||||
| @@ -886,6 +856,8 @@ export default { | ||||
|     }, | ||||
|     async toggleSights() { | ||||
|       this.showGovernorAppeal = false; | ||||
|       this.$emit("president-appeal-toggle", this.showGovernorAppeal); | ||||
|  | ||||
|       if (!this.sights.length) { | ||||
|         await this.fetchSights(); | ||||
|       } | ||||
| @@ -899,45 +871,29 @@ export default { | ||||
|     }, | ||||
|     async selectSight(sightId) { | ||||
|       this.selectedSightId = sightId; | ||||
|       const token = this.getCookie("auth_token"); | ||||
|       try { | ||||
|         const sightRes = await fetch(`https://wn-ts.krbl.ru/sight/${sightId}`, { | ||||
|           headers: { Authorization: `Bearer ${token}` }, | ||||
|         }); | ||||
|         const sightRes = await fetch(`${API_URL}/sight/${sightId}`); | ||||
|         const sightData = await sightRes.json(); | ||||
|         this.selectedSightName = sightData.name; | ||||
|  | ||||
|         const articleId = sightData.left_article; | ||||
|  | ||||
|         const articleRes = await fetch( | ||||
|           `https://wn-ts.krbl.ru/article/${articleId}`, | ||||
|           { | ||||
|             headers: { Authorization: `Bearer ${token}` }, | ||||
|           } | ||||
|         ); | ||||
|         const articleRes = await fetch(`${API_URL}/article/${articleId}`); | ||||
|         const articleData = await articleRes.json(); | ||||
|         this.selectedSightText = articleData.body; | ||||
|  | ||||
|         const mediaRes = await fetch( | ||||
|           `https://wn-ts.krbl.ru/article/${articleId}/media`, | ||||
|           { | ||||
|             headers: { Authorization: `Bearer ${token}` }, | ||||
|           } | ||||
|         ); | ||||
|         const mediaRes = await fetch(`${API_URL}/article/${articleId}/media`); | ||||
|         const mediaData = await mediaRes.json(); | ||||
|         if (mediaData.length) { | ||||
|           const imageRes = await fetch( | ||||
|             `http://31.129.106.67:8080/media/${mediaData[0].id}/download`, | ||||
|             { | ||||
|               headers: { Authorization: `Bearer ${token}` }, | ||||
|             } | ||||
|             `${API_URL}/media/${mediaData[0].id}/download` | ||||
|           ); | ||||
|           this.selectedSightImage = await imageRes.url; | ||||
|           this.selectedSightWatermarkLU = sightData.watermark_lu | ||||
|             ? `http://31.129.106.67:8080/media/${sightData.watermark_lu}/download` | ||||
|             ? `${API_URL}/media/${sightData.watermark_lu}/download` | ||||
|             : ""; | ||||
|           this.selectedSightWatermarkRD = sightData.watermark_rd | ||||
|             ? `http://31.129.106.67:8080/media/${sightData.watermark_rd}/download` | ||||
|             ? `${API_URL}/media/${sightData.watermark_rd}/download` | ||||
|             : ""; | ||||
|         } else { | ||||
|           this.selectedSightImage = ""; | ||||
| @@ -950,6 +906,8 @@ export default { | ||||
|     }, | ||||
|     async toggleList() { | ||||
|       this.showGovernorAppeal = false; | ||||
|       this.$emit("president-appeal-toggle", this.showGovernorAppeal); | ||||
|  | ||||
|       if (!this.stations.length) { | ||||
|         await this.fetchStops(); | ||||
|       } | ||||
| @@ -961,6 +919,7 @@ export default { | ||||
|       this.showSightsList = false; | ||||
|       this.showSightPreview = false; | ||||
|       this.showGovernorAppeal = false; | ||||
|       this.$emit("president-appeal-toggle", this.showGovernorAppeal); | ||||
|  | ||||
|       const root = document.documentElement; | ||||
|       root.style.setProperty( | ||||
| @@ -980,6 +939,7 @@ export default { | ||||
|           weatherInfo?.classList.remove("shifted-left"); | ||||
|         } | ||||
|       }); | ||||
|       this.resetInactivityTimer(); | ||||
|     }, | ||||
|     handleClickOutside() { | ||||
|       // пупупу | ||||
| @@ -988,10 +948,28 @@ export default { | ||||
|       console.log("Кнопка обращения губернатора нажата"); | ||||
|       this.showGovernorAppeal = !this.showGovernorAppeal; | ||||
|       this.showSightPreview = false; | ||||
|  | ||||
|       this.$emit("president-appeal-toggle", this.showGovernorAppeal); | ||||
|     }, | ||||
|     resetInactivityTimer() { | ||||
|       clearTimeout(this.inactivityTimer); | ||||
|       this.inactivityTimer = setTimeout(this.hidePanelIfActive, 300000); //  5m | ||||
|     }, | ||||
|     hidePanelIfActive() { | ||||
|       if (!this.isHidden) { | ||||
|         this.toggleCarrierInfo(); | ||||
|       } | ||||
|     }, | ||||
|     handleUserActivity() { | ||||
|       this.resetInactivityTimer(); | ||||
|     }, | ||||
|   }, | ||||
|   mounted() { | ||||
|     document.addEventListener("click", this.handleClickOutside); | ||||
|     ["mousemove", "touchstart", "keydown", "click"].forEach((evt) => | ||||
|       document.addEventListener(evt, this.handleUserActivity) | ||||
|     ); | ||||
|     this.resetInactivityTimer(); | ||||
|     const root = document.documentElement; | ||||
|     root.style.setProperty("--panel-offset", "20px"); | ||||
|     const routeInfo = document.querySelector(".routeinfo"); | ||||
| @@ -1003,6 +981,10 @@ export default { | ||||
|     console.log("hasGovernorAppeal в mounted:", this.hasGovernorAppeal); | ||||
|   }, | ||||
|   beforeUnmount() { | ||||
|     ["mousemove", "touchstart", "keydown", "click"].forEach((evt) => | ||||
|       document.removeEventListener(evt, this.handleUserActivity) | ||||
|     ); | ||||
|     clearTimeout(this.inactivityTimer); | ||||
|     document.removeEventListener("click", this.handleClickOutside); | ||||
|   }, | ||||
| }; | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -2,20 +2,64 @@ | ||||
|   <div class="routeinfo"> | ||||
|     <div class="route-number">{{ routeNumber }}</div> | ||||
|     <div class="route-names"> | ||||
|       <div class="route-name" v-if="startStopName && endStopName"> | ||||
|       <!-- Russian names --> | ||||
|       <div class="route-name ru"> | ||||
|         <div class="scroll-wrapper"> | ||||
|           <div class="scroll-inner"> | ||||
|           <div :class="isStartScrolling ? 'scroll-inner scrolling' : ''"> | ||||
|             <div class="scroll-content"> | ||||
|               <div class="name-block"> | ||||
|                 <span class="name"> | ||||
|                   {{ startStopName }} — {{ endStopName }}     | ||||
|                   {{ startStopName }} — {{ endStopName }}     | ||||
|                   {{ startStopName }} — {{ endStopName }}     | ||||
|                 <span class="name" ref="startStopRuText"> | ||||
|                   <template v-if="isStartScrolling"> | ||||
|                     {{ startStopName }}     | ||||
|                     {{ startStopName }}     {{ startStopName }} | ||||
|                   </template> | ||||
|                   <template v-else> | ||||
|                     {{ startStopName }} | ||||
|                   </template> | ||||
|                 </span> | ||||
|                 <span class="translate"> | ||||
|                   {{ startStopNameEn }} — {{ endStopNameEn }}     | ||||
|                   {{ startStopNameEn }} — {{ endStopNameEn }}     | ||||
|                   {{ startStopNameEn }} — {{ endStopNameEn }}     | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <div class="route-name ru"> | ||||
|         <div class="scroll-wrapper"> | ||||
|           <div :class="isEndScrolling ? 'scroll-inner scrolling' : ''"> | ||||
|             <div class="scroll-content"> | ||||
|               <div class="name-block"> | ||||
|                 <span class="name" ref="endStopRuText"> | ||||
|                   <template v-if="isEndScrolling"> | ||||
|                     {{ endStopName }}     | ||||
|                     {{ endStopName }}     {{ endStopName }} | ||||
|                   </template> | ||||
|                   <template v-else> | ||||
|                     {{ endStopName }} | ||||
|                   </template> | ||||
|                 </span> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <!-- English names --> | ||||
|       <div class="route-name en"> | ||||
|         <div class="scroll-wrapper"> | ||||
|           <div :class="isEnScrolling ? 'scroll-inner scrolling' : ''"> | ||||
|             <div class="scroll-content"> | ||||
|               <div class="name-block"> | ||||
|                 <span class="translate" ref="enText"> | ||||
|                   <template v-if="isEnScrolling"> | ||||
|                     {{ startStopNameEn }} — | ||||
|                     {{ endStopNameEn }}     | ||||
|                     {{ startStopNameEn }} — | ||||
|                     {{ endStopNameEn }}     | ||||
|                     {{ startStopNameEn }} — {{ endStopNameEn }} | ||||
|                   </template> | ||||
|                   <template v-else> | ||||
|                     {{ startStopNameEn }} — {{ endStopNameEn }} | ||||
|                   </template> | ||||
|                 </span> | ||||
|               </div> | ||||
|             </div> | ||||
| @@ -27,47 +71,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Cookies from "js-cookie"; | ||||
|  | ||||
| async function requestAuth() { | ||||
|   const response = await fetch("https://wn-ts.krbl.ru/auth/login", { | ||||
|     method: "POST", | ||||
|     headers: { | ||||
|       "Content-Type": "application/json", | ||||
|     }, | ||||
|     body: JSON.stringify({ | ||||
|       email: "admin", | ||||
|       password: "changeme", | ||||
|     }), | ||||
|   }); | ||||
|  | ||||
|   const data = await response.json(); | ||||
|   Cookies.set("auth_token", data.token); | ||||
|   return data.token; | ||||
| } | ||||
|  | ||||
| async function checkAuth() { | ||||
|   const token = Cookies.get("auth_token"); | ||||
|   try { | ||||
|     if (!token) { | ||||
|       return await requestAuth(); | ||||
|     } else { | ||||
|       const response = await fetch("https://wn-ts.krbl.ru/auth/me", { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }); | ||||
|  | ||||
|       if (response.status === 401) { | ||||
|         return await requestAuth(); | ||||
|       } | ||||
|     } | ||||
|     return token; | ||||
|   } catch (error) { | ||||
|     console.error("Auth check failed:", error); | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
| import { API_URL, GEO_URL } from "../config"; | ||||
|  | ||||
| export default { | ||||
|   name: "RouteInfo", | ||||
| @@ -78,37 +82,36 @@ export default { | ||||
|       endStopName: "", | ||||
|       startStopNameEn: "", | ||||
|       endStopNameEn: "", | ||||
|       isStartScrolling: false, | ||||
|       isEndScrolling: false, | ||||
|       isEnScrolling: false, | ||||
|     }; | ||||
|   }, | ||||
|   watch: { | ||||
|     startStopName() { | ||||
|       this.$nextTick(this.checkScroll); | ||||
|     }, | ||||
|     endStopName() { | ||||
|       this.$nextTick(this.checkScroll); | ||||
|     }, | ||||
|     startStopNameEn() { | ||||
|       this.$nextTick(this.checkScroll); | ||||
|     }, | ||||
|     endStopNameEn() { | ||||
|       this.$nextTick(this.checkScroll); | ||||
|     }, | ||||
|   }, | ||||
|   async mounted() { | ||||
|     const contextRes = await fetch( | ||||
|       "http://31.129.106.67:6001/v1/geolocation/context" | ||||
|     ); | ||||
|     const contextRes = await fetch(`${GEO_URL}/v1/geolocation/context`); | ||||
|     const data = await contextRes.json(); | ||||
|     this.routeNumber = data.routeNumber; | ||||
|  | ||||
|     const startStopId = data.startStopId; | ||||
|     const endStopId = data.endStopId; | ||||
|  | ||||
|     const token = await checkAuth(); | ||||
|     if (!token) { | ||||
|       console.error( | ||||
|         "No valid token received, skipping wn-ts.krbl.ru requests." | ||||
|       ); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const [startStopRes, endStopRes] = await Promise.all([ | ||||
|       fetch(`https://wn-ts.krbl.ru/station/${startStopId}`, { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }), | ||||
|       fetch(`https://wn-ts.krbl.ru/station/${endStopId}`, { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }), | ||||
|       fetch(`${API_URL}/station/${startStopId}`), | ||||
|       fetch(`${API_URL}/station/${endStopId}`), | ||||
|     ]); | ||||
|  | ||||
|     const startStopData = await startStopRes.json(); | ||||
| @@ -118,16 +121,8 @@ export default { | ||||
|     this.endStopName = endStopData.name; | ||||
|  | ||||
|     const [startStopEnRes, endStopEnRes] = await Promise.all([ | ||||
|       fetch(`https://wn-ts.krbl.ru/station/${startStopId}?lang=en`, { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }), | ||||
|       fetch(`https://wn-ts.krbl.ru/station/${endStopId}?lang=en`, { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }), | ||||
|       fetch(`${API_URL}/station/${startStopId}?lang=en`), | ||||
|       fetch(`${API_URL}/station/${endStopId}?lang=en`), | ||||
|     ]); | ||||
|  | ||||
|     const startStopEnData = await startStopEnRes.json(); | ||||
| @@ -135,7 +130,45 @@ export default { | ||||
|  | ||||
|     this.startStopNameEn = startStopEnData.name; | ||||
|     this.endStopNameEn = endStopEnData.name; | ||||
|  | ||||
|     this.$nextTick(() => { | ||||
|       this.checkScroll(); | ||||
|     }); | ||||
|   }, | ||||
|   methods: { | ||||
|     checkScroll() { | ||||
|       const threshold = 280; | ||||
|       if (this.$refs.startStopRuText) { | ||||
|         this.isStartScrolling = | ||||
|           this.$refs.startStopRuText.offsetWidth > threshold; | ||||
|       } | ||||
|       if (this.$refs.endStopRuText) { | ||||
|         this.isEndScrolling = this.$refs.endStopRuText.offsetWidth > threshold; | ||||
|       } | ||||
|       if (this.$refs.enText) { | ||||
|         this.isEnScrolling = this.$refs.enText.offsetWidth > threshold; | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   methods: {}, | ||||
| }; | ||||
| </script> | ||||
| <style scoped> | ||||
| .route-name { | ||||
|   overflow: hidden; | ||||
|   width: 280px; | ||||
|   white-space: nowrap; | ||||
| } | ||||
|  | ||||
| .scroll-inner.scrolling { | ||||
|   animation: marquee 12s linear infinite; | ||||
| } | ||||
|  | ||||
| @keyframes marquee { | ||||
|   0% { | ||||
|     transform: translateX(0); | ||||
|   } | ||||
|   100% { | ||||
|     transform: translateX(-50%); | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -98,7 +98,7 @@ | ||||
|         </div> | ||||
|       </transition> | ||||
|       <transition name="expand-collapse-transition"> | ||||
|         <div> | ||||
|         <div class="landmarks-container"> | ||||
|           <div class="landmarks" @click="toggleSightsList"> | ||||
|             <div class="landmarks-header"> | ||||
|               <svg | ||||
| @@ -149,10 +149,7 @@ | ||||
|                     :key="sight.id" | ||||
|                     class="sight-card" | ||||
|                   > | ||||
|                     <img | ||||
|                       class="sight-thumbnail" | ||||
|                       :src="`http://31.129.106.67:8080/media/${sight.thumbnail}/download`" | ||||
|                     /> | ||||
|                     <img class="sight-thumbnail" :src="sight.thumbnailUrl" /> | ||||
|                     <div class="sight-title">{{ sight.name }}</div> | ||||
|                   </div> | ||||
|                 </div> | ||||
| @@ -167,8 +164,9 @@ | ||||
|  | ||||
| <script> | ||||
| import "../assets/style/main.css"; | ||||
| import { API_URL, GEO_URL } from "../config"; | ||||
|  | ||||
| import axios from "axios"; | ||||
| import Cookies from "js-cookie"; | ||||
|  | ||||
| export default { | ||||
|   name: "StopInfo", | ||||
| @@ -176,6 +174,8 @@ export default { | ||||
|     return { | ||||
|       isModalOpen: false, | ||||
|       autoCloseTimer: null, | ||||
|       articleInactivityTimer: null, | ||||
|       sightsInactivityTimer: null, | ||||
|       imageUrl: "", | ||||
|       defaultImageUrl: | ||||
|         "https://lh3.googleusercontent.com/gps-cs-s/AB5caB8lUwofb2NIg6n0-cEl8nIWsySAUc52KNj4XezuOdo-aeqTgQlD1kTVa5MaynL2Yg4ByoTYTKNTR7K59f7kjzU9yzpudstjRiT2F6M_ilxFYFpcvMZz6OwlRFF2MrsCPSwUa7vqew=s680-w680-h510", | ||||
| @@ -246,41 +246,6 @@ export default { | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
|     async checkAuth() { | ||||
|       let token = Cookies.get("auth_token"); | ||||
|       if (!token) { | ||||
|         await this.requestAuth(); | ||||
|         return; | ||||
|       } | ||||
|       try { | ||||
|         await axios.get("https://wn-ts.krbl.ru/auth/me", { | ||||
|           headers: { | ||||
|             Authorization: `Bearer ${token}`, | ||||
|           }, | ||||
|         }); | ||||
|       } catch (e) { | ||||
|         await this.requestAuth(); | ||||
|       } | ||||
|     }, | ||||
|     async requestAuth() { | ||||
|       const response = await axios.post("https://wn-ts.krbl.ru/auth/login", { | ||||
|         email: "admin", | ||||
|         password: "changeme", | ||||
|       }); | ||||
|       const token = response.data.token; | ||||
|       Cookies.set("auth_token", token); | ||||
|       document.cookie = `auth_token=${token}; path=/;`; | ||||
|     }, | ||||
|     getCookie(name) { | ||||
|       const matches = document.cookie.match( | ||||
|         new RegExp( | ||||
|           "(?:^|; )" + | ||||
|             name.replace(/([.$?*|{}()[\]\\/+^])/g, "\\$1") + | ||||
|             "=([^;]*)" | ||||
|         ) | ||||
|       ); | ||||
|       return matches ? decodeURIComponent(matches[1]) : undefined; | ||||
|     }, | ||||
|     openModal() { | ||||
|       const imageDiv = this.$el.querySelector(".img"); | ||||
|       if (imageDiv) { | ||||
| @@ -301,74 +266,59 @@ export default { | ||||
|     }, | ||||
|     async fetchSightInfo() { | ||||
|       if (!this.sightId) { | ||||
|         console.warn("No sightId provided for fetchSightInfo"); | ||||
|         console.log("No sightId provided for fetchSightInfo"); | ||||
|         return; | ||||
|       } | ||||
|       const token = this.getCookie("auth_token"); | ||||
|       const response = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}`, | ||||
|         { | ||||
|           headers: { | ||||
|             Authorization: `Bearer ${token}`, | ||||
|           }, | ||||
|         } | ||||
|       ); | ||||
|       const response = await axios.get(`${API_URL}/sight/${this.sightId}`); | ||||
|       this.stopName = response.data.name; | ||||
|       this.watermarkLU = response.data.watermark_lu | ||||
|         ? `http://31.129.106.67:8080/media/${response.data.watermark_lu}/download` | ||||
|         : ""; | ||||
|       this.watermarkRD = response.data.watermark_rd | ||||
|         ? `http://31.129.106.67:8080/media/${response.data.watermark_rd}/download` | ||||
|         : ""; | ||||
|       if (response.data.watermark_lu) { | ||||
|         this.watermarkLU = await this.getMediaBlobUrl( | ||||
|           response.data.watermark_lu | ||||
|         ); | ||||
|       } else { | ||||
|         this.watermarkLU = ""; | ||||
|       } | ||||
|       if (response.data.watermark_rd) { | ||||
|         this.watermarkRD = await this.getMediaBlobUrl( | ||||
|           response.data.watermark_rd | ||||
|         ); | ||||
|       } else { | ||||
|         this.watermarkRD = ""; | ||||
|       } | ||||
|     }, | ||||
|     async fetchArticles() { | ||||
|       if (!this.sightId) { | ||||
|         console.warn("No sightId provided for fetchArticles"); | ||||
|         return; | ||||
|       } | ||||
|       const token = this.getCookie("auth_token"); | ||||
|       const response = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}/article`, | ||||
|         { | ||||
|           headers: { | ||||
|             Authorization: `Bearer ${token}`, | ||||
|           }, | ||||
|         } | ||||
|         `${API_URL}/sight/${this.sightId}/article` | ||||
|       ); | ||||
|       this.articles = response.data; | ||||
|       if (this.articles.length > 0) { | ||||
|         this.selectArticle(this.articles[0].id); | ||||
|         this.resetArticleInactivityTimer(); | ||||
|       } | ||||
|     }, | ||||
|     async fetchSights() { | ||||
|       const token = this.getCookie("auth_token"); | ||||
|       const geoRes = await axios.get( | ||||
|         "http://31.129.106.67:6001/v1/geolocation/context" | ||||
|       ); | ||||
|       const geoRes = await axios.get(`${GEO_URL}/v1/geolocation/context`); | ||||
|       const routeId = geoRes.data.routeId; | ||||
|       if (!routeId) { | ||||
|         console.warn("Missing routeId in geo context:", geoRes.data); | ||||
|         return; | ||||
|       } | ||||
|       const sightsRes = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/route/${routeId}/sight`, | ||||
|         { | ||||
|           headers: { Authorization: `Bearer ${token}` }, | ||||
|         } | ||||
|       ); | ||||
|       const sightsRes = await axios.get(`${API_URL}/route/${routeId}/sight`); | ||||
|       const rawSights = sightsRes.data; | ||||
|       const detailedSights = await Promise.all( | ||||
|         rawSights.map(async (sight) => { | ||||
|           const detailRes = await axios.get( | ||||
|             `https://wn-ts.krbl.ru/sight/${sight.id}`, | ||||
|             { | ||||
|               headers: { Authorization: `Bearer ${token}` }, | ||||
|             } | ||||
|           ); | ||||
|           const detailRes = await axios.get(`${API_URL}/sight/${sight.id}`); | ||||
|           const thumbnailUrl = detailRes.data.thumbnail | ||||
|             ? await this.getMediaBlobUrl(detailRes.data.thumbnail) | ||||
|             : ""; | ||||
|           return { | ||||
|             id: sight.id, | ||||
|             name: detailRes.data.name, | ||||
|             thumbnail: detailRes.data.thumbnail, | ||||
|             thumbnailUrl, | ||||
|           }; | ||||
|         }) | ||||
|       ); | ||||
| @@ -376,27 +326,63 @@ export default { | ||||
|     }, | ||||
|     toggleSightsList() { | ||||
|       this.showSightsList = !this.showSightsList; | ||||
|       if (this.showSightsList) { | ||||
|         this.resetSightsInactivityTimer(); | ||||
|       } else if (this.sightsInactivityTimer) { | ||||
|         clearTimeout(this.sightsInactivityTimer); | ||||
|         this.sightsInactivityTimer = null; | ||||
|       } | ||||
|     }, | ||||
|     resetSightsInactivityTimer() { | ||||
|       if (this.articleInactivityTimer) { | ||||
|         clearTimeout(this.articleInactivityTimer); | ||||
|       } | ||||
|       if (this.sightsInactivityTimer) { | ||||
|         clearTimeout(this.sightsInactivityTimer); | ||||
|       } | ||||
|       if (this.showSightsList) { | ||||
|         this.sightsInactivityTimer = setTimeout(() => { | ||||
|           this.showSightsList = false; | ||||
|           this.sightsInactivityTimer = null; | ||||
|         }, 300000); // 5 m | ||||
|       } | ||||
|     }, | ||||
|     resetArticleInactivityTimer() { | ||||
|       if (this.articleInactivityTimer) | ||||
|         clearTimeout(this.articleInactivityTimer); | ||||
|  | ||||
|       this.articleInactivityTimer = setTimeout(() => { | ||||
|         if ( | ||||
|           this.articles.length > 0 && | ||||
|           this.selectedArticleId !== this.articles[0].id | ||||
|         ) { | ||||
|           this.selectArticle(this.articles[0].id); | ||||
|         } | ||||
|       }, 300_000); // 5 m | ||||
|     }, | ||||
|     handleUserActivity() { | ||||
|       if (this.showSightsList) this.resetSightsInactivityTimer(); | ||||
|       this.resetArticleInactivityTimer(); | ||||
|     }, | ||||
|     selectArticle(id) { | ||||
|       this.resetArticleInactivityTimer(); | ||||
|       this.selectedArticleId = id; | ||||
|       const selected = this.articles.find((article) => article.id === id); | ||||
|       this.selectedArticleBody = selected ? selected.body : ""; | ||||
|       this.fetchArticleMedia(id); | ||||
|     }, | ||||
|     async fetchArticleMedia(articleId) { | ||||
|       const token = this.getCookie("auth_token"); | ||||
|       try { | ||||
|         const response = await axios.get( | ||||
|           `https://wn-ts.krbl.ru/article/${articleId}/media`, | ||||
|           { | ||||
|             headers: { | ||||
|               Authorization: `Bearer ${token}`, | ||||
|             }, | ||||
|           } | ||||
|           `${API_URL}/article/${articleId}/media` | ||||
|         ); | ||||
|         if (response.data && response.data.length > 0) { | ||||
|           const mediaId = response.data[0].id; | ||||
|           this.imageUrl = `http://31.129.106.67:8080/media/${mediaId}/download`; | ||||
|           try { | ||||
|             this.imageUrl = await this.getMediaBlobUrl(mediaId); | ||||
|           } catch { | ||||
|             this.imageUrl = `${API_URL}/media/${mediaId}/download`; | ||||
|           } | ||||
|         } else { | ||||
|           this.imageUrl = ""; | ||||
|         } | ||||
| @@ -405,39 +391,60 @@ export default { | ||||
|         this.imageUrl = ""; | ||||
|       } | ||||
|     }, | ||||
|     async fetchGeolocationContext() { | ||||
|  | ||||
|     async getMediaBlobUrl(mediaId) { | ||||
|       try { | ||||
|         const response = await axios.get( | ||||
|           "http://31.129.106.67:6001/v1/geolocation/context" | ||||
|         ); | ||||
|         this.routeProgress = response.data.routeProgress; | ||||
|         console.log("Updated routeProgress:", this.routeProgress); | ||||
|         const token = this.getCookie("auth_token"); | ||||
|         const stopsResponse = await axios.get( | ||||
|           `https://wn-ts.krbl.ru/route/${response.data.routeId}/station`, | ||||
|           `${API_URL}/media/${mediaId}/download`, | ||||
|           { | ||||
|             headers: { Authorization: `Bearer ${token}` }, | ||||
|             responseType: "blob", | ||||
|           } | ||||
|         ); | ||||
|         return URL.createObjectURL(response.data); | ||||
|       } catch (error) { | ||||
|         console.error( | ||||
|           `Failed to download media ${mediaId}:`, | ||||
|           error?.response?.status, | ||||
|           error?.response?.data || error.message | ||||
|         ); | ||||
|         return `${API_URL}/media/${mediaId}/download`; | ||||
|       } | ||||
|     }, | ||||
|     async fetchGeolocationContext() { | ||||
|       try { | ||||
|         const response = await axios.get(`${GEO_URL}/v1/geolocation/context`); | ||||
|         this.routeProgress = response.data.routeProgress; | ||||
|         const stopsResponse = await axios.get( | ||||
|           `${API_URL}/route/${response.data.routeId}/station` | ||||
|         ); | ||||
|         this.stops = stopsResponse.data; | ||||
|         const newSightId = response.data.nearestSightId; | ||||
|         let newSightId = response.data.nearestSightId; | ||||
|  | ||||
|         if (!newSightId) { | ||||
|           if (!this.sights || this.sights.length === 0) { | ||||
|             await this.fetchSights(); | ||||
|           } | ||||
|           if (this.sights && this.sights.length > 0) { | ||||
|             newSightId = this.sights[1].id; | ||||
|           } | ||||
|         } | ||||
|         if (newSightId && newSightId !== this.sightId) { | ||||
|           this.sightId = newSightId; | ||||
|           await this.fetchSightInfo(); | ||||
|           await this.fetchArticles(); | ||||
|         } | ||||
|         const nextStopId = response.data.routeProgress?.endStopId; | ||||
|         console.log("Fetched next stop ID:", nextStopId); | ||||
|         console.log("Stops:", this.stops); | ||||
|         // console.log("Fetched next stop ID:", nextStopId); | ||||
|         // console.log("Stops:", this.stops); | ||||
|         if (nextStopId && this.stops) { | ||||
|           const nextStop = this.stops.find((stop) => stop.id == nextStopId); | ||||
|           console.log("Fetched next stop ID:", nextStopId); | ||||
|           console.log("Matching stop:", nextStop); | ||||
|           // console.log("Fetched next stop ID:", nextStopId); | ||||
|           // console.log("Matching stop:", nextStop); | ||||
|           if (nextStop && nextStop.transfers) { | ||||
|             console.log("Transfers at next stop:", nextStop.transfers); | ||||
|             // console.log("Transfers at next stop:", nextStop.transfers); | ||||
|             this.nextStopTransfers = nextStop.transfers; | ||||
|           } else { | ||||
|             console.log("No transfers found at next stop"); | ||||
|             // console.log("No transfers found at next stop"); | ||||
|             this.nextStopTransfers = null; | ||||
|           } | ||||
|         } | ||||
| @@ -456,7 +463,7 @@ export default { | ||||
|     toggleTransfers() { | ||||
|       console.log("Transfer toggle clicked"); | ||||
|       this.showTransfers = !this.showTransfers; | ||||
|       console.log("showTransfers:", this.showTransfers); | ||||
|       // console.log("showTransfers:", this.showTransfers); | ||||
|  | ||||
|       if (!this.showTransfers) { | ||||
|         return; | ||||
| @@ -468,32 +475,46 @@ export default { | ||||
|       } | ||||
|  | ||||
|       const nextStopId = this.routeProgress.endStopId; | ||||
|       console.log("Next stop ID from routeProgress:", nextStopId); | ||||
|       console.log("All stops:", this.stops); | ||||
|       // console.log("Next stop ID from routeProgress:", nextStopId); | ||||
|       // console.log("All stops:", this.stops); | ||||
|  | ||||
|       const nextStop = this.stops.find((stop) => stop.id === nextStopId); | ||||
|       console.log("Next stop object:", nextStop); | ||||
|       // console.log("Next stop object:", nextStop); | ||||
|  | ||||
|       if (nextStop && nextStop.transfers) { | ||||
|         console.log("Transfers at next stop:", nextStop.transfers); | ||||
|         // console.log("Transfers at next stop:", nextStop.transfers); | ||||
|         this.nextStopTransfers = nextStop.transfers; | ||||
|       } else { | ||||
|         console.log("No transfers found at next stop"); | ||||
|         // console.log("No transfers found at next stop"); | ||||
|         this.nextStopTransfers = null; | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   async mounted() { | ||||
|     await this.checkAuth(); | ||||
|     await this.fetchGeolocationContext(); | ||||
|     await this.fetchSights(); | ||||
|     await this.fetchGeolocationContext(); | ||||
|     this.geolocationInterval = setInterval(() => { | ||||
|       this.fetchGeolocationContext(); | ||||
|     }, 1000); | ||||
|     window.addEventListener("mousemove", this.handleUserActivity); | ||||
|     window.addEventListener("touchstart", this.handleUserActivity, { | ||||
|       passive: true, | ||||
|     }); | ||||
|     window.addEventListener("keydown", this.handleUserActivity); | ||||
|     window.addEventListener("click", this.handleUserActivity, true); | ||||
|     // this.fetchSightInfo(); | ||||
|     // this.fetchArticles(); | ||||
|   }, | ||||
|   unmounted() { | ||||
|     if (this.sightsInactivityTimer) { | ||||
|       clearTimeout(this.sightsInactivityTimer); | ||||
|     } | ||||
|     window.removeEventListener("mousemove", this.handleUserActivity); | ||||
|     window.removeEventListener("touchstart", this.handleUserActivity, { | ||||
|       passive: true, | ||||
|     }); | ||||
|     window.removeEventListener("keydown", this.handleUserActivity); | ||||
|     window.removeEventListener("click", this.handleUserActivity, true); | ||||
|     if (this.autoCloseTimer) { | ||||
|       clearTimeout(this.autoCloseTimer); | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
|   <div class="weatherinfo"> | ||||
|   <div class="weatherinfo" :class="{ hidden: isPresidentAddressOpen }"> | ||||
|     <div class="time">{{ formattedTime }}</div> | ||||
|     <div class="date">{{ formattedDate }}, {{ dayOfWeek }}</div> | ||||
|  | ||||
| @@ -10,7 +10,7 @@ | ||||
|           alt="Weather Icon" | ||||
|         /> | ||||
|         <div class="temperature-celsius"> | ||||
|           {{ currentWeather.temperatureCelsius }}°C | ||||
|           {{ currentWeather.temperatureCelsius }}° | ||||
|         </div> | ||||
|         <div> | ||||
|           {{ currentWeather.description }} | ||||
| @@ -27,8 +27,8 @@ | ||||
|           <div class="humidity"> | ||||
|             <svg | ||||
|               xmlns="http://www.w3.org/2000/svg" | ||||
|               width="12" | ||||
|               height="16" | ||||
|               width="13" | ||||
|               height="18" | ||||
|               fill="none" | ||||
|               viewBox="0 0 12 16" | ||||
|             > | ||||
| @@ -43,8 +43,8 @@ | ||||
|           <div class="wind"> | ||||
|             <svg | ||||
|               xmlns="http://www.w3.org/2000/svg" | ||||
|               width="16" | ||||
|               height="16" | ||||
|               width="18" | ||||
|               height="18" | ||||
|               fill="none" | ||||
|               viewBox="0 0 16 16" | ||||
|             > | ||||
| @@ -72,8 +72,9 @@ | ||||
|  | ||||
| <script> | ||||
| import "../assets/style/main.css"; | ||||
| import { API_URL } from "../config"; | ||||
|  | ||||
| import axios from "axios"; | ||||
| import Cookies from "js-cookie"; | ||||
| import clearIcon from "@/icons/clear-day.svg"; | ||||
| import cloudsIcon from "@/icons/cloudy.svg"; | ||||
| import rainIcon from "@/icons/rainy.svg"; | ||||
| @@ -84,6 +85,12 @@ import fogIcon from "@/icons/fog.svg"; | ||||
|  | ||||
| export default { | ||||
|   name: "WeatherInfo", | ||||
|   props: { | ||||
|     isPresidentAddressOpen: { | ||||
|       type: Boolean, | ||||
|       default: false, | ||||
|     }, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       isModalOpen: false, | ||||
| @@ -101,55 +108,24 @@ export default { | ||||
|       dayOfWeek: "", | ||||
|       timerInterval: null, | ||||
|       weatherDescriptionMap: { | ||||
|         thunderstorm: "Гроза", | ||||
|         drizzle: "Слабый дождь", | ||||
|         rain: "Дождь", | ||||
|         snow: "Снег", | ||||
|         atmosphere: "Туман", | ||||
|         clear: "Ясно", | ||||
|         clouds: "Облачно", | ||||
|         thunderstorm: "гроза", | ||||
|         drizzle: "слабый дождь", | ||||
|         rain: "дождь", | ||||
|         snow: "снег", | ||||
|         atmosphere: "туман", | ||||
|         clear: "ясно", | ||||
|         clouds: "облачно", | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
|     async getToken() { | ||||
|       let token = Cookies.get("auth_token"); | ||||
|       try { | ||||
|         await axios.get("https://wn-ts.krbl.ru/auth/me", { | ||||
|           headers: { Authorization: `Bearer ${token}` }, | ||||
|         }); | ||||
|       } catch (error) { | ||||
|         if (error.response && error.response.status === 401) { | ||||
|           const response = await axios.post( | ||||
|             "https://wn-ts.krbl.ru/auth/login", | ||||
|             { | ||||
|               email: "admin", | ||||
|               password: "changeme", | ||||
|             } | ||||
|           ); | ||||
|           token = response.data.token; | ||||
|           Cookies.set("auth_token", token); | ||||
|         } | ||||
|       } | ||||
|       return token; | ||||
|     }, | ||||
|     async fetchSightInfo() { | ||||
|       const token = await this.getToken(); | ||||
|       const response = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}`, | ||||
|         { | ||||
|           headers: { Authorization: `Bearer ${token}` }, | ||||
|         } | ||||
|       ); | ||||
|       const response = await axios.get(`${API_URL}/sight/${this.sightId}`); | ||||
|       this.stopName = response.data.name; | ||||
|     }, | ||||
|     async fetchArticles() { | ||||
|       const token = await this.getToken(); | ||||
|       const response = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}/article`, | ||||
|         { | ||||
|           headers: { Authorization: `Bearer ${token}` }, | ||||
|         } | ||||
|         `${API_URL}/sight/${this.sightId}/article` | ||||
|       ); | ||||
|       this.articles = response.data; | ||||
|       if (this.articles.length > 0) { | ||||
|   | ||||
							
								
								
									
										2
									
								
								src/config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| export const API_URL = process.env.VUE_APP_API_URL; | ||||
| export const GEO_URL = process.env.VUE_APP_GEO_URL; | ||||
							
								
								
									
										35
									
								
								src/icons/tram-bottom-left.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-bottom-left.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-bottom-right.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-bottom-right.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-left.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-left.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-right.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-right.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-top-left.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-top-left.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										35
									
								
								src/icons/tram-top-right.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/icons/tram-top-right.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 63 KiB | 
		Reference in New Issue
	
	Block a user