181 lines
6.2 KiB
Vue
181 lines
6.2 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* Универсальный экран ошибки для 404 / 403 / 500. Конфигурация через
|
|
* `route.meta.errorCode` ('404' | '403' | '500').
|
|
*
|
|
* Источник дизайна: liderra_v8_handoff/concepts/v8_errors.html.
|
|
*
|
|
* Layout: meta.layout='error' — AppShell рендерит ErrorView напрямую без
|
|
* AppLayout/AuthLayout (full-bleed теало-нуар bg + центрированный card).
|
|
*
|
|
* Sprint 4 Phase B/3 — split на ErrorBrand + ErrorIllustration + ErrorActions
|
|
* + ErrorMeta (audit O-refactor-04 закрытие). State (config, errorCode) — в parent
|
|
* ради единого route.meta-driven flow.
|
|
*
|
|
* Не входит в этот коммит:
|
|
* - Реальный requestId/incidentId из backend (сейчас mock).
|
|
* - Status-pills из API /api/health (сейчас static на 500).
|
|
* - Sentry capture при mount 500 (для production).
|
|
*/
|
|
import { computed } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import ErrorBrand from '../../components/errors/ErrorBrand.vue';
|
|
import ErrorIllustration from '../../components/errors/ErrorIllustration.vue';
|
|
import ErrorActions from '../../components/errors/ErrorActions.vue';
|
|
import ErrorMeta from '../../components/errors/ErrorMeta.vue';
|
|
import DevIndexBadge from '../../components/DevIndexBadge.vue';
|
|
|
|
interface ErrorAction {
|
|
label: string;
|
|
icon: string;
|
|
to?: string;
|
|
href?: string;
|
|
onClick?: () => void;
|
|
}
|
|
|
|
interface ErrorConfig {
|
|
code: '404' | '403' | '500';
|
|
title: string;
|
|
description: string;
|
|
primaryAction: ErrorAction;
|
|
secondaryAction?: ErrorAction;
|
|
showRequestId?: boolean;
|
|
requestIdLabel?: string;
|
|
requestId?: string;
|
|
showStatusList?: boolean;
|
|
}
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const errorCode = computed<'404' | '403' | '500'>(
|
|
() => (route.meta.errorCode as '404' | '403' | '500' | undefined) ?? '404',
|
|
);
|
|
|
|
const config = computed<ErrorConfig>(() => {
|
|
if (errorCode.value === '403') {
|
|
return {
|
|
code: '403',
|
|
title: 'У вас нет доступа',
|
|
description:
|
|
'Эта страница принадлежит другому тенанту, либо ваша роль не позволяет её увидеть. Если вы считаете, что это ошибка — обратитесь к администратору вашей команды или в поддержку.',
|
|
primaryAction: {
|
|
label: 'На дашборд',
|
|
icon: 'mdi-view-dashboard-outline',
|
|
to: '/dashboard',
|
|
},
|
|
secondaryAction: {
|
|
label: 'Написать в поддержку',
|
|
icon: 'mdi-email-outline',
|
|
href: 'mailto:support@liderra.app',
|
|
},
|
|
showRequestId: true,
|
|
requestIdLabel: 'Запрос',
|
|
requestId: 'REQ-3F8A2-0007',
|
|
};
|
|
}
|
|
if (errorCode.value === '500') {
|
|
return {
|
|
code: '500',
|
|
title: 'Что-то пошло не так',
|
|
description:
|
|
'Внутренняя ошибка — мы уже занимаемся. Команда получила уведомление. Большинство сбоев чинятся за 5–10 минут. Можно вернуться через минуту, или открыть страницу статуса.',
|
|
primaryAction: {
|
|
label: 'Попробовать снова',
|
|
icon: 'mdi-refresh',
|
|
onClick: () => location.reload(),
|
|
},
|
|
secondaryAction: {
|
|
label: 'Статус сервиса',
|
|
icon: 'mdi-pulse',
|
|
href: 'https://status.liderra.app',
|
|
},
|
|
showRequestId: true,
|
|
requestIdLabel: 'Инцидент',
|
|
requestId: 'INC-2026-0507-0034',
|
|
showStatusList: true,
|
|
};
|
|
}
|
|
// default 404
|
|
return {
|
|
code: '404',
|
|
title: 'Страница не найдена',
|
|
description:
|
|
'Похоже, такой страницы нет — её удалили, переименовали или вы ввели адрес с опечаткой. Все рабочие экраны Лидерра доступны через дашборд.',
|
|
primaryAction: {
|
|
label: 'На дашборд',
|
|
icon: 'mdi-view-dashboard-outline',
|
|
to: '/dashboard',
|
|
},
|
|
secondaryAction: {
|
|
label: 'Назад',
|
|
icon: 'mdi-arrow-left',
|
|
onClick: () => router.back(),
|
|
},
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<v-app>
|
|
<v-main class="error-main">
|
|
<ErrorBrand />
|
|
|
|
<div class="error-content">
|
|
<ErrorIllustration :code="config.code" />
|
|
<h2 class="err-title">{{ config.title }}</h2>
|
|
<p class="err-desc">{{ config.description }}</p>
|
|
|
|
<ErrorActions :primary="config.primaryAction" :secondary="config.secondaryAction" />
|
|
|
|
<ErrorMeta
|
|
:code="config.code"
|
|
:show-status-list="config.showStatusList"
|
|
:show-request-id="config.showRequestId"
|
|
:request-id-label="config.requestIdLabel"
|
|
:request-id="config.requestId"
|
|
/>
|
|
</div>
|
|
</v-main>
|
|
<DevIndexBadge :index="route.meta.devIndex" :label="route.meta.devLabel" />
|
|
</v-app>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.error-main {
|
|
background: #012019;
|
|
color: #fff;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.error-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 24px 32px 80px;
|
|
text-align: center;
|
|
max-width: 560px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.err-title {
|
|
font-size: 28px;
|
|
font-weight: 600;
|
|
font-variation-settings: 'opsz' 28;
|
|
letter-spacing: -0.018em;
|
|
margin: 0 0 12px;
|
|
color: #fff;
|
|
}
|
|
|
|
.err-desc {
|
|
color: #b1c2bd;
|
|
line-height: 1.55;
|
|
font-size: 15px;
|
|
margin: 0 0 24px;
|
|
}
|
|
</style>
|