generated from VLADIMIR/template_frontend
update
This commit is contained in:
parent
073afde7c0
commit
0aac04e9bd
4
Makefile
Normal file
4
Makefile
Normal file
@ -0,0 +1,4 @@
|
||||
build:
|
||||
npm run build
|
||||
rm -rf ../pinned_message/cmd/pinned_message/static/user
|
||||
cp -r dist ../pinned_message/cmd/pinned_message/static/user
|
||||
82
src/App.vue
82
src/App.vue
@ -1,85 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
import SchedulePage from './components/SchedulePage.vue';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
|
||||
|
||||
<div class="wrapper">
|
||||
<HelloWorld msg="You did it!" />
|
||||
|
||||
<nav>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/about">About</RouterLink>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<RouterView />
|
||||
<div>
|
||||
<SchedulePage></SchedulePage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
line-height: 1.5;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: inline-block;
|
||||
padding: 0 1rem;
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
nav a:first-of-type {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
padding-right: calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0 2rem 0 0;
|
||||
}
|
||||
|
||||
header .wrapper {
|
||||
display: flex;
|
||||
place-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav {
|
||||
text-align: left;
|
||||
margin-left: -1rem;
|
||||
font-size: 1rem;
|
||||
|
||||
padding: 1rem 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
padding: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
59
src/components/SchedulePage.vue
Normal file
59
src/components/SchedulePage.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { apiGetDays } from './client';
|
||||
import type { Schedule } from './models';
|
||||
import { formatRussianDate, getRelativeDayName } from './date';
|
||||
import { capitalizeFirstLetter } from './text';
|
||||
|
||||
const schedule = ref<Schedule>({ days: [] })
|
||||
|
||||
onMounted(async () => {
|
||||
schedule.value = await apiGetDays()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="day in schedule.days" :key="day.date" class="day-block">
|
||||
{{ capitalizeFirstLetter(formatRussianDate(day.date)) }}
|
||||
<span v-if="getRelativeDayName(day.date) !== ''">({{ getRelativeDayName(day.date) }})</span>
|
||||
<div v-for="performance in day.performances" :key="performance.name" class="performance-block">
|
||||
<div>
|
||||
{{ performance.time_collection }}
|
||||
</div>
|
||||
<div>
|
||||
{{ performance.time_start }}
|
||||
</div>
|
||||
<div>
|
||||
Место: {{ capitalizeFirstLetter(performance.place) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ capitalizeFirstLetter(performance.name) }}
|
||||
</div>
|
||||
<div>
|
||||
Номера:
|
||||
<div v-for="number in performance.numbers" :key="number.name">
|
||||
- {{ number.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="performance.costumes !== '' && performance.costumes !== '-'">
|
||||
{{ capitalizeFirstLetter(performance.costumes) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.day-block {
|
||||
border: solid 1px rebeccapurple;
|
||||
margin: 10px 0;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.performance-block {
|
||||
|
||||
}
|
||||
</style>
|
||||
27
src/components/client.ts
Normal file
27
src/components/client.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { Schedule } from './models'
|
||||
|
||||
export const apiGetDays = async (): Promise<Schedule> => {
|
||||
try {
|
||||
const response = await fetch(getApiUrl('/schedule'), { method: 'GET' })
|
||||
if (!response.ok) {
|
||||
throw new Error(`http error status: ${response.status}`)
|
||||
}
|
||||
return await response.json()
|
||||
} catch (error) {
|
||||
console.error('[apiGetDays] error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function getApiUrl(path: string) {
|
||||
const url = 'http://' + window.location.host.split(':')[0] + ':8090' + path
|
||||
return url
|
||||
}
|
||||
|
||||
export function encodeUTF8ToBase64(s: string) {
|
||||
return btoa(
|
||||
encodeURIComponent(s).replace(/%([0-9A-F]{2})/g, (_, p1) =>
|
||||
String.fromCharCode(parseInt(p1, 16)),
|
||||
),
|
||||
)
|
||||
}
|
||||
75
src/components/date.ts
Normal file
75
src/components/date.ts
Normal file
@ -0,0 +1,75 @@
|
||||
export function formatRussianDate(dateStr: string): string {
|
||||
// 1. Вытаскиваем только саму дату (2026-03-24) с помощью регулярного выражения
|
||||
// Это защитит нас от багов с часовыми поясами при парсинге
|
||||
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
if (!match) {
|
||||
throw new Error("Неверный формат входной строки");
|
||||
}
|
||||
|
||||
// 2. Преобразуем строки в числа
|
||||
const year = parseInt(match[1], 10);
|
||||
const month = parseInt(match[2], 10);
|
||||
const day = parseInt(match[3], 10);
|
||||
|
||||
// 3. Создаем объект даты (обратите внимание: в JS месяцы начинаются с 0, поэтому month - 1)
|
||||
const dateObj = new Date(year, month - 1, day);
|
||||
|
||||
// 4. Словари для точного форматирования (родительный падеж для месяцев)
|
||||
const monthsRu = [
|
||||
"января", "февраля", "марта", "апреля", "мая", "июня",
|
||||
"июля", "августа", "сентября", "октября", "ноября", "декабря"
|
||||
];
|
||||
|
||||
// В JS 0 - это воскресенье, 1 - понедельник и т.д.
|
||||
const weekdaysRu = [
|
||||
"воскресенье", "понедельник", "вторник", "среда",
|
||||
"четверг", "пятница", "суббота"
|
||||
];
|
||||
|
||||
// 5. Получаем нужные значения
|
||||
const monthName = monthsRu[dateObj.getMonth()];
|
||||
const weekdayName = weekdaysRu[dateObj.getDay()];
|
||||
|
||||
// 6. Собираем итоговую строку
|
||||
return `${weekdayName} ${day} ${monthName}`;
|
||||
}
|
||||
|
||||
export function getRelativeDayName(dateStr: string): string {
|
||||
// 1. Вытаскиваем только дату (например, 2026-03-24)
|
||||
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
if (!match) {
|
||||
throw new Error("Неверный формат входной строки");
|
||||
}
|
||||
|
||||
const year = parseInt(match[1], 10);
|
||||
const month = parseInt(match[2], 10) - 1; // Месяцы в JS начинаются с 0
|
||||
const day = parseInt(match[3], 10);
|
||||
|
||||
// 2. Создаем объект целевой даты и сбрасываем время в 00:00:00
|
||||
const targetDate = new Date(year, month, day, 0, 0, 0, 0);
|
||||
|
||||
// 3. Создаем объект СЕГОДНЯШНЕЙ даты и тоже сбрасываем время в 00:00:00
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
// 4. Вычисляем разницу в миллисекундах и переводим в дни
|
||||
// Используем Math.round, чтобы избежать багов при переходе на летнее/зимнее время
|
||||
const diffInMs = targetDate.getTime() - today.getTime();
|
||||
const diffInDays = Math.round(diffInMs / (1000 * 60 * 60 * 24));
|
||||
|
||||
// 5. Возвращаем результат в зависимости от разницы в днях
|
||||
switch (diffInDays) {
|
||||
case 0:
|
||||
return "сегодня";
|
||||
case 1:
|
||||
return "завтра";
|
||||
case 2:
|
||||
return "послезавтра"; // бонус :)
|
||||
case -1:
|
||||
return "вчера"; // бонус :)
|
||||
default:
|
||||
// Если это не сегодня и не завтра, можно вернуть пустую строку,
|
||||
// либо просто количество дней, либо отформатированную дату (как в прошлом ответе)
|
||||
return "";
|
||||
}
|
||||
}
|
||||
21
src/components/models.ts
Normal file
21
src/components/models.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export type Schedule = {
|
||||
days: Day[]
|
||||
}
|
||||
|
||||
export type Day = {
|
||||
date: string
|
||||
performances: Performance[]
|
||||
}
|
||||
|
||||
export type Performance = {
|
||||
time_collection: string
|
||||
time_start: string
|
||||
place: string
|
||||
name: string
|
||||
numbers: Number[]
|
||||
costumes: string
|
||||
}
|
||||
|
||||
export type Number = {
|
||||
name: string
|
||||
}
|
||||
9
src/components/text.ts
Normal file
9
src/components/text.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export function capitalizeFirstLetter(str: string): string {
|
||||
// Проверка на пустую строку, чтобы избежать ошибок
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// Берем первый символ, делаем его большим + приклеиваем остаток строки (начиная со 2-го символа)
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user