2941b14b38
- Moved contents of main_dc/yalarba/easySite/easySite/ up to easySite/ - Updated docker-compose.yml build context path - Deleted empty nested easySite/ directory
413 lines
15 KiB
Vue
413 lines
15 KiB
Vue
<template>
|
||
<div class="profile-edit-page">
|
||
<div class="container container-large">
|
||
<div class="page-header">
|
||
<h1 class="page-title">Редактирование профиля</h1>
|
||
<p class="page-subtitle">Обновите информацию о вашем аккаунте</p>
|
||
</div>
|
||
|
||
<div v-if="auth.user.value" class="profile-layout">
|
||
<!-- Основная форма редактирования -->
|
||
<div class="profile-main">
|
||
<!-- Личная информация -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h2 class="card-title">Личная информация</h2>
|
||
</div>
|
||
<div class="card-body">
|
||
<form class="profile-form" @submit.prevent="saveProfile">
|
||
<div class="user-avatar-section">
|
||
<div class="user-avatar">
|
||
<span class="avatar-text">{{ userInitials }}</span>
|
||
</div>
|
||
<div class="avatar-actions">
|
||
<button type="button" class="btn btn-outline btn-sm">Изменить фото</button>
|
||
<button type="button" class="btn btn-outline btn-sm">Удалить</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- В форме редактирования профиля -->
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label for="fullName" class="form-label">Полное имя</label>
|
||
<input
|
||
id="fullName"
|
||
v-model="formData.full_name"
|
||
type="text"
|
||
class="form-input"
|
||
placeholder="Введите ваше полное имя"
|
||
autocomplete="name">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="inn" class="form-label">ИНН</label>
|
||
<input
|
||
id="inn"
|
||
v-model="formData.inn"
|
||
type="text"
|
||
class="form-input"
|
||
placeholder="Введите ваш ИНН"
|
||
maxlength="12"
|
||
autocomplete="off">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="phone" class="form-label">Телефон</label>
|
||
<input
|
||
id="phone"
|
||
v-model="formData.phone"
|
||
type="tel"
|
||
class="form-input"
|
||
placeholder="+7 (___) ___-__-__"
|
||
autocomplete="tel">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="city" class="form-label">Город</label>
|
||
<input
|
||
id="city"
|
||
v-model="formData.city"
|
||
type="text"
|
||
class="form-input"
|
||
placeholder="Введите ваш город"
|
||
autocomplete="address-level2">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="email" class="form-label">Email</label>
|
||
<div class="email-input-group">
|
||
<input
|
||
id="email"
|
||
v-model="formData.email"
|
||
type="email"
|
||
class="form-input"
|
||
:class="{ 'input-error': emailError }"
|
||
placeholder="Введите ваш email"
|
||
autocomplete="email"
|
||
required
|
||
@blur="validateEmail">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary" :disabled="isSaving">
|
||
{{ isSaving ? 'Сохранение...' : 'Сохранить изменения' }}
|
||
</button>
|
||
<NuxtLink to="/profile" class="btn btn-outline">Отмена</NuxtLink>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Информация об организации -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h2 class="card-title">Информация об организации</h2>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label for="orgType" class="form-label">Форма организации</label>
|
||
<select id="orgType" v-model="formData.org_type" class="form-select">
|
||
<option value="">Не выбрано</option>
|
||
<option value="individual">Индивидуальный предприниматель (ИП)</option>
|
||
<option value="llc">Общество с ограниченной ответственностью (ООО)</option>
|
||
<option value="jsc">Акционерное общество (АО)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="orgFullName" class="form-label">Полное название организации</label>
|
||
<input
|
||
id="orgFullName"
|
||
v-model="formData.org_full_name"
|
||
type="text"
|
||
class="form-input"
|
||
placeholder="Введите полное название организации">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="orgShortName" class="form-label">Короткое название организации</label>
|
||
<input
|
||
id="orgShortName"
|
||
v-model="formData.org_short_name"
|
||
type="text"
|
||
class="form-input"
|
||
placeholder="Введите короткое название организации">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="orgInn" class="form-label">ИНН организации</label>
|
||
<input
|
||
id="orgInn"
|
||
v-model="formData.org_inn"
|
||
type="text"
|
||
class="form-input"
|
||
placeholder="Введите ИНН организации" maxlength="12">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Боковая панель -->
|
||
<div class="profile-sidebar">
|
||
<!-- Статус -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">Статус аккаунта</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="status-item">
|
||
<span class="status-icon">✅</span>
|
||
<span class="status-text">Активен</span>
|
||
</div>
|
||
<div v-if="formData.org_type" class="status-item">
|
||
<span class="status-icon">🏢</span>
|
||
<span class="status-text">{{ orgTypeText }}</span>
|
||
</div>
|
||
<p class="status-note">Демо-режим</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Навигация -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">Навигация</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="nav-links">
|
||
<NuxtLink to="/profile" class="nav-link-block">
|
||
👤 Мой профиль
|
||
</NuxtLink>
|
||
<NuxtLink to="/objects/my-objects" class="nav-link-block">
|
||
📝 Мои объекты
|
||
</NuxtLink>
|
||
<NuxtLink to="/objects/create" class="nav-link-block">
|
||
➕ Добавить объект
|
||
</NuxtLink>
|
||
<NuxtLink to="/" class="nav-link-block">
|
||
🏠 Главная
|
||
</NuxtLink>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Загрузка -->
|
||
<div v-else-if="auth.loading.value" class="loading-container">
|
||
<div class="loading-spinner"/>
|
||
<p>Загрузка профиля...</p>
|
||
</div>
|
||
|
||
<!-- Ошибка -->
|
||
<div v-else class="error-container">
|
||
<p>Не удалось загрузить данные профиля</p>
|
||
<button class="btn btn-primary" @click="reload">Попробовать снова</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
const auth = useAuth()
|
||
const router = useRouter()
|
||
|
||
// Инициализация данных пользователя
|
||
onMounted(async () => {
|
||
if (!auth.user.value) {
|
||
await auth.checkAuth()
|
||
}
|
||
|
||
// Заполняем форму данными пользователя
|
||
if (auth.user.value) {
|
||
formData.value = {
|
||
full_name: auth.user.value.full_name || '',
|
||
inn: auth.user.value.inn || '',
|
||
phone: auth.user.value.phone || '',
|
||
city: auth.user.value.city || '',
|
||
email: auth.user.value.email || '',
|
||
org_type: auth.user.value.org_type || '',
|
||
org_full_name: auth.user.value.org_full_name || '',
|
||
org_short_name: auth.user.value.org_short_name || '',
|
||
org_inn: auth.user.value.org_inn || ''
|
||
}
|
||
}
|
||
})
|
||
|
||
// Состояние формы
|
||
const formData = ref({
|
||
full_name: '',
|
||
inn: '',
|
||
phone: '',
|
||
city: '',
|
||
email: '',
|
||
org_type: '',
|
||
org_full_name: '',
|
||
org_short_name: '',
|
||
org_inn: ''
|
||
})
|
||
|
||
// Состояние для верификации email
|
||
const isEmailVerified = ref(false)
|
||
const showVerificationModal = ref(false)
|
||
const verificationCode = ref('')
|
||
const isSendingCode = ref(false)
|
||
const isVerifyingCode = ref(false)
|
||
const verificationError = ref('')
|
||
const emailError = ref('')
|
||
const isSaving = ref(false)
|
||
|
||
// Вычисляем инициалы пользователя для аватара
|
||
const userInitials = computed(() => {
|
||
if (!formData.value.full_name) return '??'
|
||
|
||
const names = formData.value.full_name.split(' ')
|
||
if (names.length >= 2 && names[0] && names[1]) {
|
||
return `${names[0][0]}${names[1][0]}`.toUpperCase()
|
||
}
|
||
return formData.value.full_name.substring(0, 2).toUpperCase()
|
||
})
|
||
|
||
// Текст для типа организации
|
||
const orgTypeText = computed(() => {
|
||
const types: Record<string, string> = {
|
||
individual: 'Индивидуальный предприниматель',
|
||
llc: 'Общество с ограниченной ответственностью',
|
||
jsc: 'Акционерное общество'
|
||
}
|
||
return types[formData.value.org_type] || 'Не указано'
|
||
})
|
||
|
||
// Валидация email
|
||
const validateEmail = () => {
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||
if (!formData.value.email) {
|
||
emailError.value = 'Email обязателен для заполнения'
|
||
} else if (!emailRegex.test(formData.value.email)) {
|
||
emailError.value = 'Введите корректный email адрес'
|
||
} else {
|
||
emailError.value = ''
|
||
}
|
||
}
|
||
|
||
// Отправка кода верификации
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||
const sendVerificationCode = async () => {
|
||
isSendingCode.value = true
|
||
verificationError.value = ''
|
||
|
||
// Имитация запроса к API
|
||
try {
|
||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||
showVerificationModal.value = true
|
||
console.log(`Код верификации отправлен на ${formData.value.email}`)
|
||
} catch (error) {
|
||
console.log(error)
|
||
verificationError.value = 'Ошибка при отправке кода. Попробуйте позже.'
|
||
} finally {
|
||
isSendingCode.value = false
|
||
}
|
||
}
|
||
|
||
// Верификация кода
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||
const verifyEmailCode = async () => {
|
||
isVerifyingCode.value = true
|
||
verificationError.value = ''
|
||
|
||
// Имитация проверки кода
|
||
try {
|
||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||
|
||
// В демо-режиме любой 6-значный код считается верным
|
||
if (verificationCode.value.length === 6) {
|
||
isEmailVerified.value = true
|
||
showVerificationModal.value = false
|
||
verificationCode.value = ''
|
||
} else {
|
||
verificationError.value = 'Неверный код подтверждения'
|
||
}
|
||
} catch (error) {
|
||
console.log("debug. Error: " + error)
|
||
verificationError.value = 'Ошибка при проверке кода. Попробуйте позже.'
|
||
} finally {
|
||
isVerifyingCode.value = false
|
||
}
|
||
}
|
||
|
||
// Закрытие модального окна верификации
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||
const closeVerificationModal = () => {
|
||
showVerificationModal.value = false
|
||
verificationCode.value = ''
|
||
verificationError.value = ''
|
||
}
|
||
|
||
// Сохранение профиля
|
||
const saveProfile = async () => {
|
||
isSaving.value = true
|
||
|
||
// Валидация формы
|
||
validateEmail()
|
||
|
||
if (emailError.value) {
|
||
isSaving.value = false
|
||
return
|
||
}
|
||
|
||
// Имитация сохранения данных
|
||
try {
|
||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||
console.log('Профиль сохранен:', formData.value)
|
||
|
||
// В реальном приложении здесь был бы запрос к API
|
||
// После успешного сохранения перенаправляем на страницу профиля
|
||
router.push('/profile')
|
||
} catch (error) {
|
||
console.error('Ошибка при сохранении профиля:', error)
|
||
} finally {
|
||
isSaving.value = false
|
||
}
|
||
}
|
||
|
||
const reload = () => {
|
||
window.location.reload()
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* Стили остаются такими же как в оригинальном edit.vue */
|
||
/* Добавляем стили для состояний загрузки и ошибок */
|
||
.loading-container,
|
||
.error-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 4rem 2rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 3rem;
|
||
height: 3rem;
|
||
border: 3px solid var(--border-light);
|
||
border-top: 3px solid var(--primary-500);
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% {
|
||
transform: rotate(0deg);
|
||
}
|
||
|
||
100% {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
</style> |