394663597f
- SettingsView (/settings): sidebar tabs-rail (md=3, 8 v-list-item с mdi-icon) + content-pane (md=9 v-card outlined min-height 480px). activeTab ref переключает рендер вкладки. Реализованы: - ProfileTab: avatar 80px + 5 form-fields (имя/email disabled/телефон/TZ/роль). - SecurityTab: 3 cards (Пароль / 2FA включена + recovery codes + Отключить / Активные сессии 3 mock с Завершить-btn). - ApiTab: API-ключ password+eye-toggle + Webhook (URL + signing secret HMAC). Текст про дедуп (tenant_id, source_crm_id) 24ч и антифрод по phone (§10.8.1). - NotificationsTab: матрица 8x3 (events × channels) соответствует schema v8.7 §4 users.notification_preferences JSONB. 8 событий (new_lead, duplicate_detected, low_balance, tariff_charge, reminder_due, manager_assigned, webhook_failed, monthly_report) × 3 канала (email/sms/in_app). + sound_enabled switch. Placeholder: - PlaceholderTab универсальный с props title/description + v-alert «В разработке». - Используется для Проекты / Команда / Интеграции / Тихие часы. Маршрут /settings (meta.layout=app, lazy-import) в router + web.php. .gitleaks.toml: settings/*.vue в allowlist (фиктивный профиль). cspell-words.txt: смыслово. Vitest +8 (всего 98/98 за 8.42s): - 8 nav-tabs + все названия + дефолт «Профиль» + Проекты → «В разработке» + Уведомления показывает «События × каналы» + 5 событий матрицы + Безопасность: 2FA + сессии + API: API-ключ + Signing secret HMAC. Регресс: lint+type+format OK; vitest 98/98; vite build (SettingsView lazy-chunk; main app-chunk 107.85KB); story:build 17/24 за 31.7s; Pest 48/48 за 5.03s. CLAUDE.md v1.27->v1.28, реестр Открытых_вопросов v1.36->v1.37. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
124 lines
4.9 KiB
Vue
124 lines
4.9 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* Settings — настройки тенанта/пользователя. 8 вкладок (по v8.5 §13 + ТЗ §14).
|
||
*
|
||
* Источник дизайна: liderra_v8_handoff/concepts/v8_settings.html.
|
||
* Полностью реализованы (с UI-разводкой): Профиль, Безопасность, API и Webhook,
|
||
* Уведомления (матрица 8×3 по schema v8.7 §4 users.notification_preferences).
|
||
* Placeholder-заглушки: Проекты, Команда, Интеграции, Тихие часы.
|
||
*/
|
||
import { computed, ref } from 'vue';
|
||
import ApiTab from './settings/ApiTab.vue';
|
||
import NotificationsTab from './settings/NotificationsTab.vue';
|
||
import PlaceholderTab from './settings/PlaceholderTab.vue';
|
||
import ProfileTab from './settings/ProfileTab.vue';
|
||
import SecurityTab from './settings/SecurityTab.vue';
|
||
|
||
interface Tab {
|
||
id: string;
|
||
label: string;
|
||
icon: string;
|
||
}
|
||
|
||
const tabs: Tab[] = [
|
||
{ id: 'profile', label: 'Профиль', icon: 'mdi-account-outline' },
|
||
{ id: 'security', label: 'Безопасность', icon: 'mdi-shield-lock-outline' },
|
||
{ id: 'projects', label: 'Проекты', icon: 'mdi-folder-outline' },
|
||
{ id: 'team', label: 'Команда', icon: 'mdi-account-group-outline' },
|
||
{ id: 'api', label: 'API и Webhook', icon: 'mdi-api' },
|
||
{ id: 'integrations', label: 'Интеграции', icon: 'mdi-puzzle-outline' },
|
||
{ id: 'hours', label: 'Тихие часы', icon: 'mdi-clock-outline' },
|
||
{ id: 'notifications', label: 'Уведомления', icon: 'mdi-bell-outline' },
|
||
];
|
||
|
||
const activeTab = ref('profile');
|
||
|
||
const placeholderProps = computed(() => {
|
||
const map: Record<string, { title: string; description: string }> = {
|
||
projects: {
|
||
title: 'Проекты',
|
||
description:
|
||
'Управление проектами тенанта (макс. 10 на тарифе «Команда»). Для каждого проекта — поставщик ГЦК, цена за лид, активные UTM-кампании.',
|
||
},
|
||
team: {
|
||
title: 'Команда',
|
||
description:
|
||
'Менеджеры тенанта (макс. 4 + расширение). Назначение прав, автораспределение, ограничение доступа к проектам.',
|
||
},
|
||
integrations: {
|
||
title: 'Интеграции',
|
||
description:
|
||
'Подключение Telegram-бота для нотификаций, экспорт в 1С 8.3, JivoSite helpdesk, Yandex 360 SSO.',
|
||
},
|
||
hours: {
|
||
title: 'Тихие часы',
|
||
description:
|
||
'Расписание, в которое не приходят SMS/звонки автонапоминаний (например, 22:00-08:00 + выходные).',
|
||
},
|
||
};
|
||
return map[activeTab.value];
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<v-container fluid class="settings pa-6">
|
||
<header class="page-head">
|
||
<h1 class="text-h4 mb-2 page-title">Настройки</h1>
|
||
<p class="text-body-2 text-medium-emphasis ma-0">Профиль, безопасность, API и интеграции</p>
|
||
</header>
|
||
|
||
<v-row class="settings-row mt-4">
|
||
<v-col cols="12" md="3">
|
||
<v-card variant="outlined" class="tabs-rail pa-2">
|
||
<v-list density="compact" nav>
|
||
<v-list-item
|
||
v-for="tab in tabs"
|
||
:key="tab.id"
|
||
:prepend-icon="tab.icon"
|
||
:active="activeTab === tab.id"
|
||
rounded="lg"
|
||
@click="activeTab = tab.id"
|
||
>
|
||
<v-list-item-title>{{ tab.label }}</v-list-item-title>
|
||
</v-list-item>
|
||
</v-list>
|
||
</v-card>
|
||
</v-col>
|
||
|
||
<v-col cols="12" md="9">
|
||
<v-card variant="outlined" class="tab-pane pa-6">
|
||
<ProfileTab v-if="activeTab === 'profile'" />
|
||
<SecurityTab v-else-if="activeTab === 'security'" />
|
||
<ApiTab v-else-if="activeTab === 'api'" />
|
||
<NotificationsTab v-else-if="activeTab === 'notifications'" />
|
||
<PlaceholderTab
|
||
v-else-if="placeholderProps"
|
||
:title="placeholderProps.title"
|
||
:description="placeholderProps.description"
|
||
/>
|
||
</v-card>
|
||
</v-col>
|
||
</v-row>
|
||
</v-container>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.settings {
|
||
max-width: 1440px;
|
||
}
|
||
|
||
.page-title {
|
||
font-variation-settings: 'opsz' 28;
|
||
letter-spacing: -0.018em;
|
||
}
|
||
|
||
.tabs-rail {
|
||
background: #fff;
|
||
}
|
||
|
||
.tab-pane {
|
||
background: #fff;
|
||
min-height: 480px;
|
||
}
|
||
</style>
|