Files
tp/main_dc/yalarba/easySite/easySite/app/pages/profile/edit.vue
T
valitovgaziz 1ba3f907ac new file: main_dc/yalarba/easySite/easySite/app/components/ObjectCard.vue
new file:   main_dc/yalarba/easySite/easySite/app/components/ObjectForm.vue
	deleted:    main_dc/yalarba/easySite/easySite/app/components/forms/ObjectForm.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/objects/[id]/edit.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/objects/[id]/index.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/objects/create.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/objects/index.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/objects/my-objects.vue
	deleted:    main_dc/yalarba/easySite/easySite/app/pages/objects/search.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/profile/edit.vue
add pages for object, objectsSearch, editObject, createObject
2025-11-07 13:09:30 +05:00

715 lines
20 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 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 @submit.prevent="saveProfile" class="profile-form">
<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 type="text" id="fullName" v-model="formData.fullName" class="form-input"
placeholder="Введите ваше полное имя">
</div>
<div class="form-group">
<label for="inn" class="form-label">ИНН</label>
<input type="text" id="inn" v-model="formData.inn" class="form-input" placeholder="Введите ваш ИНН"
maxlength="12">
</div>
<div class="form-group">
<label for="phone" class="form-label">Телефон</label>
<input type="tel" id="phone" v-model="formData.phone" class="form-input"
placeholder="+7 (___) ___-__-__">
</div>
<div class="form-group">
<label for="city" class="form-label">Город</label>
<input type="text" id="city" v-model="formData.city" class="form-input"
placeholder="Введите ваш город">
</div>
<div class="form-group">
<label for="email" class="form-label">Email</label>
<div class="email-input-group">
<input type="email" id="email" v-model="formData.email" class="form-input"
:class="{ 'input-error': emailError }" placeholder="Введите ваш email" @blur="validateEmail">
<div v-if="emailError" class="error-message">{{ emailError }}</div>
<div v-if="!isEmailVerified && formData.email" class="email-verification">
<div class="verification-status">
<span class="status-icon"></span>
<span class="status-text">Email не подтвержден</span>
</div>
<button type="button" class="btn btn-outline btn-sm" @click="sendVerificationCode"
:disabled="isSendingCode">
{{ isSendingCode ? 'Отправка...' : 'Подтвердить email' }}
</button>
</div>
<div v-if="isEmailVerified" class="email-verification verified">
<div class="verification-status">
<span class="status-icon"></span>
<span class="status-text">Email подтвержден</span>
</div>
</div>
</div>
</div>
<!-- Модальное окно для верификации email -->
<div v-if="showVerificationModal" class="modal-overlay">
<div class="modal-content">
<div class="modal-header">
<h3>Подтверждение email</h3>
<button type="button" class="modal-close" @click="closeVerificationModal">×</button>
</div>
<div class="modal-body">
<p>Мы отправили код подтверждения на адрес <strong>{{ formData.email }}</strong></p>
<div class="verification-code-group">
<label for="verificationCode" class="form-label">Введите код подтверждения</label>
<input type="text" id="verificationCode" v-model="verificationCode" class="form-input"
placeholder="Введите 6-значный код" maxlength="6">
<div v-if="verificationError" class="error-message">{{ verificationError }}</div>
<div class="verification-actions">
<button type="button" class="btn btn-primary" @click="verifyEmailCode"
:disabled="!verificationCode || verificationCode.length !== 6">
Подтвердить
</button>
<button type="button" class="btn btn-outline" @click="sendVerificationCode"
:disabled="isSendingCode">
{{ isSendingCode ? 'Отправка...' : 'Отправить код повторно' }}
</button>
</div>
</div>
</div>
</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.orgType" class="form-select">
<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 type="text" id="orgFullName" v-model="formData.orgFullName" class="form-input"
placeholder="Введите полное название организации">
</div>
<div class="form-group">
<label for="orgShortName" class="form-label">Короткое название организации</label>
<input type="text" id="orgShortName" v-model="formData.orgShortName" class="form-input"
placeholder="Введите короткое название организации">
</div>
<div class="form-group">
<label for="orgInn" class="form-label">ИНН организации</label>
<input type="text" id="orgInn" v-model="formData.orgInn" 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 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>
</div>
</template>
<script setup lang="ts">
// Импортируем композиции Nuxt
const router = useRouter();
// Состояние формы
const formData = ref({
fullName: 'Иванов Иван Петрович',
inn: '123456789012',
phone: '+7 (999) 123-45-67',
city: 'Москва',
email: 'user@example.com',
orgType: 'individual',
orgFullName: 'Индивидуальный предприниматель Иванов Иван Петрович',
orgShortName: 'ИП Иванов И.П.',
orgInn: '123456789012'
});
// Состояние для верификации 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(() => {
const names = formData.value.fullName.split(' ');
if (names.length >= 2) {
return `${names[0][0]}${names[1][0]}`.toUpperCase();
}
return formData.value.fullName.substring(0, 2).toUpperCase();
});
// Текст для типа организации
const orgTypeText = computed(() => {
const types = {
individual: 'Индивидуальный предприниматель',
llc: 'Общество с ограниченной ответственностью',
jsc: 'Акционерное общество'
};
return types[formData.value.orgType as keyof typeof types] || 'Не указано';
});
// Валидация 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 = '';
}
};
// Отправка кода верификации
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) {
verificationError.value = 'Ошибка при отправке кода. Попробуйте позже.';
} finally {
isSendingCode.value = false;
}
};
// Верификация кода
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;
}
};
// Закрытие модального окна верификации
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;
}
};
</script>
<style scoped>
.profile-edit-page {
min-height: 100vh;
background: var(--bg-secondary);
padding: 2rem 0;
}
.container-large {
max-width: 1200px;
}
.page-header {
margin-bottom: 2rem;
}
.page-title {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.page-subtitle {
color: var(--text-secondary);
font-size: 1.125rem;
}
.profile-layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
}
.profile-main {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.profile-sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.profile-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.user-avatar-section {
display: flex;
align-items: center;
gap: 1.5rem;
margin-bottom: 1rem;
}
.user-avatar {
width: 5rem;
height: 5rem;
background: var(--primary-100);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-text {
color: var(--primary-600);
font-weight: bold;
font-size: 1.5rem;
}
.avatar-actions {
display: flex;
gap: 0.5rem;
}
.form-grid {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-label {
font-weight: 500;
color: var(--text-secondary);
font-size: 0.875rem;
}
.form-input,
.form-select {
padding: var(--space-sm) var(--space-md);
border: 1px solid var(--border-medium);
border-radius: var(--radius-md);
font-size: 1rem;
transition: all 0.2s;
background: var(--bg-primary);
color: var(--text-primary);
}
.form-input:focus,
.form-select:focus {
outline: none;
border-color: var(--primary-500);
box-shadow: 0 0 0 3px rgb(14 165 233 / 0.1);
}
.form-input.input-error {
border-color: var(--error-500);
}
.email-input-group {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.error-message {
color: var(--error-500);
font-size: 0.875rem;
}
.email-verification {
padding: 0.75rem;
border-radius: var(--radius-md);
background: var(--warning-50);
border: 1px solid var(--warning-200);
}
.email-verification.verified {
background: var(--success-50);
border-color: var(--success-200);
}
.verification-status {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.status-icon {
font-size: 1.125rem;
}
.status-text {
font-weight: 500;
color: var(--text-primary);
}
.form-actions {
display: flex;
gap: 1rem;
margin-top: 1rem;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-xs);
padding: var(--space-sm) var(--space-lg);
border: none;
border-radius: var(--radius-md);
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
font-size: 0.875rem;
line-height: 1.25rem;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background: var(--primary-500);
color: var(--text-inverse);
}
.btn-primary:hover:not(:disabled) {
background: var(--primary-600);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-outline {
background: transparent;
border: 1px solid var(--border-medium);
color: var(--text-secondary);
}
.btn-outline:hover:not(:disabled) {
background: var(--gray-50);
border-color: var(--primary-500);
color: var(--primary-600);
}
.btn-sm {
padding: var(--space-xs) var(--space-md);
font-size: 0.75rem;
}
/* Модальное окно верификации */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.modal-content {
background: var(--bg-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
width: 100%;
max-width: 500px;
overflow: hidden;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-lg);
border-bottom: 1px solid var(--border-light);
}
.modal-header h3 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--text-secondary);
}
.modal-body {
padding: var(--space-lg);
}
.verification-code-group {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
}
.verification-actions {
display: flex;
gap: 1rem;
}
.verification-actions .btn {
flex: 1;
}
/* Боковая панель */
.status-item {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.status-icon {
font-size: 1.125rem;
}
.status-text {
font-weight: 500;
color: var(--success-600);
}
.status-note {
color: var(--text-secondary);
font-size: 0.875rem;
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid var(--border-light);
}
.nav-links {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.nav-link-block {
display: block;
padding: 0.75rem 1rem;
border-radius: var(--radius-md);
color: var(--text-secondary);
text-decoration: none;
transition: all 0.2s;
font-weight: 500;
}
.nav-link-block:hover {
color: var(--primary-600);
background: var(--primary-50);
}
/* Адаптивность */
@media (max-width: 968px) {
.profile-layout {
grid-template-columns: 1fr;
}
.user-avatar-section {
flex-direction: column;
text-align: center;
}
.avatar-actions {
justify-content: center;
}
.form-actions {
flex-direction: column;
}
}
@media (max-width: 768px) {
.page-title {
font-size: 1.5rem;
}
.verification-actions {
flex-direction: column;
}
}
@media (max-width: 480px) {
.profile-edit-page {
padding: 1rem 0;
}
.page-header {
margin-bottom: 1.5rem;
}
.profile-main,
.profile-sidebar {
gap: 1rem;
}
.modal-content {
margin: 1rem;
}
}
</style>