Доработка сессий
This commit is contained in:
@@ -54,7 +54,13 @@ export const authApi = {
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'logout' })
|
||||
}, true) // skipSessionCheck — это выход
|
||||
}, true), // skipSessionCheck — это выход
|
||||
logoutAll: () => request('/api/user', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'logout_all' })
|
||||
}, true) // skipSessionCheck — выход со всех устройств
|
||||
}
|
||||
|
||||
// ==================== PROJECTS ====================
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
<button
|
||||
v-if="dialogShowDiscard"
|
||||
class="btn-discard"
|
||||
:class="dialogDiscardVariant"
|
||||
@click="handleDiscard"
|
||||
:disabled="loading"
|
||||
>
|
||||
{{ dialogDiscardText }}
|
||||
<span v-if="loading === 'discard'" class="btn-loader"></span>
|
||||
<span v-else>{{ dialogDiscardText }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn-confirm"
|
||||
@@ -22,7 +24,7 @@
|
||||
@click="handleConfirm"
|
||||
:disabled="loading"
|
||||
>
|
||||
<span v-if="loading" class="btn-loader"></span>
|
||||
<span v-if="loading === 'confirm'" class="btn-loader"></span>
|
||||
<span v-else>{{ dialogConfirmText }}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -56,10 +58,16 @@ const props = defineProps({
|
||||
default: undefined
|
||||
},
|
||||
variant: String,
|
||||
discardVariant: String,
|
||||
// Async callback для подтверждения — сам управляет loading
|
||||
action: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
// Async callback для discard (опционально)
|
||||
discardAction: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -71,19 +79,20 @@ const dialogTitle = computed(() => props.title ?? config.value.title ?? 'Под
|
||||
const dialogMessage = computed(() => props.message ?? config.value.message ?? 'Вы уверены?')
|
||||
const dialogConfirmText = computed(() => props.confirmText ?? config.value.confirmText ?? 'Подтвердить')
|
||||
const dialogCancelText = computed(() => props.cancelText ?? 'Отмена')
|
||||
const dialogDiscardText = computed(() => props.discardText ?? 'Не сохранять')
|
||||
const dialogDiscardText = computed(() => props.discardText ?? config.value.discardText ?? 'Не сохранять')
|
||||
const dialogShowDiscard = computed(() => props.showDiscard ?? config.value.showDiscard ?? false)
|
||||
const dialogVariant = computed(() => props.variant ?? config.value.variant ?? 'default')
|
||||
const dialogDiscardVariant = computed(() => props.discardVariant ?? config.value.discardVariant ?? 'default')
|
||||
|
||||
const emit = defineEmits(['confirm', 'cancel', 'discard'])
|
||||
|
||||
// Внутреннее состояние загрузки
|
||||
const loading = ref(false)
|
||||
// Внутреннее состояние загрузки: null | 'confirm' | 'discard'
|
||||
const loading = ref(null)
|
||||
|
||||
// Сброс состояния при закрытии диалога
|
||||
watch(() => props.show, (newVal) => {
|
||||
if (!newVal) {
|
||||
loading.value = false
|
||||
loading.value = null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -92,7 +101,7 @@ const handleConfirm = async () => {
|
||||
|
||||
// Если есть async action — вызываем его и управляем loading
|
||||
if (props.action) {
|
||||
loading.value = true
|
||||
loading.value = 'confirm'
|
||||
try {
|
||||
await props.action()
|
||||
// Успех — эмитим confirm для закрытия
|
||||
@@ -101,7 +110,7 @@ const handleConfirm = async () => {
|
||||
console.error('ConfirmDialog action failed:', e)
|
||||
// При ошибке — не закрываем диалог
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = null
|
||||
}
|
||||
} else {
|
||||
// Простой режим — просто эмитим
|
||||
@@ -114,9 +123,26 @@ const handleCancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const handleDiscard = () => {
|
||||
const handleDiscard = async () => {
|
||||
if (loading.value) return
|
||||
emit('discard')
|
||||
|
||||
// Если есть async discardAction — вызываем его и управляем loading
|
||||
if (props.discardAction) {
|
||||
loading.value = 'discard'
|
||||
try {
|
||||
await props.discardAction()
|
||||
// Успех — эмитим discard для закрытия
|
||||
emit('discard')
|
||||
} catch (e) {
|
||||
console.error('ConfirmDialog discardAction failed:', e)
|
||||
// При ошибке — не закрываем диалог
|
||||
} finally {
|
||||
loading.value = null
|
||||
}
|
||||
} else {
|
||||
// Простой режим — просто эмитим
|
||||
emit('discard')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -196,6 +222,30 @@ const handleDiscard = () => {
|
||||
background: rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
|
||||
.btn-discard.warning {
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.btn-discard.warning:hover {
|
||||
background: rgba(245, 158, 11, 0.25);
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
.btn-discard.danger {
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-discard.danger:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.btn-discard:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
@@ -206,21 +256,23 @@ const handleDiscard = () => {
|
||||
}
|
||||
|
||||
.btn-confirm.danger {
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
.btn-confirm.danger:hover {
|
||||
background: #dc2626;
|
||||
background: rgba(239, 68, 68, 0.25);
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
.btn-confirm.warning {
|
||||
background: #f59e0b;
|
||||
color: #000;
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.btn-confirm.warning:hover {
|
||||
background: #d97706;
|
||||
background: rgba(245, 158, 11, 0.25);
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
.btn-confirm:disabled {
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
<ConfirmDialog
|
||||
v-model:show="showDialog"
|
||||
type="logout"
|
||||
:action="logout"
|
||||
:action="logoutAll"
|
||||
:discard-action="logoutCurrent"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -38,13 +39,22 @@ const router = useRouter()
|
||||
const store = useProjectsStore()
|
||||
const showDialog = ref(false)
|
||||
|
||||
const logout = async () => {
|
||||
// Выход с текущей сессии (discard action)
|
||||
const logoutCurrent = async () => {
|
||||
clearAuthCache()
|
||||
await authApi.logout()
|
||||
store.reset()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
// Выход со всех сессий (confirm action)
|
||||
const logoutAll = async () => {
|
||||
clearAuthCache()
|
||||
await authApi.logoutAll()
|
||||
store.reset()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
const refreshIcons = () => {
|
||||
if (window.lucide) window.lucide.createIcons()
|
||||
}
|
||||
|
||||
@@ -92,12 +92,15 @@ export const DIALOGS = {
|
||||
variant: 'danger'
|
||||
},
|
||||
|
||||
// Выход из системы
|
||||
// Выход из системы (3 кнопки)
|
||||
logout: {
|
||||
title: 'Выйти из аккаунта?',
|
||||
message: 'Вы будете перенаправлены<br>на страницу входа.',
|
||||
confirmText: 'Выйти',
|
||||
variant: 'warning'
|
||||
message: 'Выберите, откуда хотите выйти',
|
||||
confirmText: 'Все сессии',
|
||||
discardText: 'Текущая сессия',
|
||||
showDiscard: true,
|
||||
variant: 'danger',
|
||||
discardVariant: 'warning'
|
||||
},
|
||||
|
||||
// Удаление отдела
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
<div class="float-icon icon-12"><i data-lucide="message-circle"></i></div>
|
||||
</div>
|
||||
|
||||
<!-- Градиентные сферы на фоне -->
|
||||
<div class="bg-glow glow-2"></div>
|
||||
<div class="bg-glow glow-3"></div>
|
||||
|
||||
<!-- Контент авторизации -->
|
||||
<div class="login-content" :class="{ 'is-loading': isProcessing, 'is-success': showSuccess }">
|
||||
|
||||
@@ -489,44 +485,6 @@ watch([showSuccess, isRegisterMode], () => {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Градиентные сферы на фоне */
|
||||
.bg-glow {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(120px);
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.glow-2 {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: radial-gradient(circle, rgba(96, 165, 250, 0.25) 0%, transparent 70%);
|
||||
bottom: -150px;
|
||||
left: -100px;
|
||||
animation: glowFloat2 10s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.glow-3 {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, rgba(244, 114, 182, 0.2) 0%, transparent 70%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: glowFloat3 12s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes glowFloat2 {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(40px, -20px) scale(1.05); }
|
||||
}
|
||||
|
||||
@keyframes glowFloat3 {
|
||||
0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
|
||||
50% { transform: translate(-50%, -50%) scale(1.2); opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* Левитирующие иконки */
|
||||
.floating-icons {
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user