Визуальные правки приложения
This commit is contained in:
BIN
public/assets/noise.png
Normal file
BIN
public/assets/noise.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
1
public/assets/topography.svg
Normal file
1
public/assets/topography.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 64 KiB |
22
resources/js/Components/Noise.vue
Normal file
22
resources/js/Components/Noise.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
opacity: {
|
||||
type: Number,
|
||||
default: 0.025,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="pointer-events-none fixed inset-0 z-9999 size-full"
|
||||
:style="{
|
||||
backgroundImage: 'url(/assets/noise.png)',
|
||||
opacity: props.opacity,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@ import {NLayout, NLayoutHeader, NEl, NAlert, NSpin, NText} from "naive-ui";
|
||||
import AppHeader from "./Components/AppHeader.vue";
|
||||
import {useGlobalLoading} from "../Composables/useGlobalLoading.js";
|
||||
import AppDialogManager from "../Components/AppDialogManager.vue";
|
||||
import Noise from "../Components/Noise.vue";
|
||||
import { useThemeVars } from "naive-ui";
|
||||
import { computed } from "vue";
|
||||
|
||||
@@ -22,6 +23,7 @@ const blobBg = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Noise />
|
||||
<AppDialogManager />
|
||||
<NLayout position="absolute">
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import {Head, router, useForm, usePage} from '@inertiajs/vue3'
|
||||
import { useAuthStore } from '../../Stores/auth.js'
|
||||
import { TbUser, TbLock } from 'vue-icons-plus/tb'
|
||||
@@ -7,8 +7,42 @@ import {TbHeartRateMonitor} from 'vue-icons-plus/tb'
|
||||
import {
|
||||
NForm, NFormItem, NInput, NButton, NCheckbox,
|
||||
NSpace, NCard, NIcon, NAlert, NModal, darkTheme,
|
||||
NConfigProvider, NLayout, NP, NEl
|
||||
NConfigProvider, NLayout, NP, useThemeVars
|
||||
} from 'naive-ui'
|
||||
import Noise from '../../Components/Noise.vue'
|
||||
|
||||
const topographySvg = '/assets/topography.svg'
|
||||
|
||||
const themeVars = useThemeVars()
|
||||
|
||||
const blobBg = computed(() => {
|
||||
const p = themeVars.value.primaryColor
|
||||
const w = themeVars.value.warningColor
|
||||
const i = themeVars.value.infoColor
|
||||
return [
|
||||
`radial-gradient(circle 18vw at 8% 8%, color-mix(in srgb, ${p} 26%, transparent), transparent 100%)`,
|
||||
`radial-gradient(circle 22vw at 100% 18%, color-mix(in srgb, ${i} 22%, transparent), transparent 100%)`,
|
||||
`radial-gradient(circle 24vw at 12% 100%, color-mix(in srgb, ${w} 20%, transparent), transparent 100%)`,
|
||||
].join(', ')
|
||||
})
|
||||
|
||||
const primaryColor = computed(() => themeVars.value.primaryColor)
|
||||
const infoColor = computed(() => themeVars.value.infoColor)
|
||||
const cardColor = computed(() => themeVars.value.cardColor)
|
||||
const borderColor = computed(() => themeVars.value.borderColor)
|
||||
|
||||
// Интерактивная текстура фона (следует за курсором/пальцем)
|
||||
const texturePos = reactive({ x: 0, y: 0 })
|
||||
|
||||
const onTexturePointerMove = (e) => {
|
||||
const point = e.touches ? e.touches[0] : e
|
||||
texturePos.x = point.clientX
|
||||
texturePos.y = point.clientY
|
||||
}
|
||||
|
||||
const textureMaskImage = computed(() => {
|
||||
return `url(${topographySvg}), radial-gradient(circle 280px at ${texturePos.x}px ${texturePos.y}px, #fff 0%, transparent 100%)`
|
||||
})
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const page = usePage()
|
||||
@@ -81,20 +115,34 @@ const handleForgotPassword = async () => {
|
||||
<template>
|
||||
<NConfigProvider :theme="darkTheme">
|
||||
<NLayout embedded>
|
||||
<div class="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div
|
||||
class="relative min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8 overflow-hidden"
|
||||
@mousemove="onTexturePointerMove"
|
||||
@touchmove="onTexturePointerMove"
|
||||
>
|
||||
<Head title="Вход в систему" />
|
||||
|
||||
<div class="max-w-md w-full space-y-8">
|
||||
<!-- Цветные градиентные пятна фона -->
|
||||
<!-- <div class="pointer-events-none fixed inset-0 z-0" :style="`background: ${blobBg};`" /> -->
|
||||
|
||||
<Noise />
|
||||
|
||||
<!-- Базовый слой топографической текстуры -->
|
||||
<div class="texture-layer texture-base" />
|
||||
<!-- Подсветка текстуры вокруг курсора -->
|
||||
<div class="texture-layer texture-highlight" :style="{ maskImage: textureMaskImage, WebkitMaskImage: textureMaskImage }" />
|
||||
|
||||
<div class="auth-enter relative z-10 max-w-md w-full space-y-8">
|
||||
<!-- Логотип и заголовок -->
|
||||
<div class="text-center">
|
||||
<div class="flex justify-center">
|
||||
<NEl class="w-16 h-16 rounded-full flex items-center justify-center" style="background-color: var(--primary-color);">
|
||||
<NIcon size="32" color="var(--base-color)">
|
||||
<div class="auth-logo flex items-center justify-center">
|
||||
<NIcon size="30" color="#fff">
|
||||
<TbHeartRateMonitor />
|
||||
</NIcon>
|
||||
</NEl>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="mt-6 text-3xl font-bold">
|
||||
<h2 class="mt-6 text-3xl font-bold tracking-tight">
|
||||
Метрика
|
||||
</h2>
|
||||
<NP class="mt-2! text-sm" depth="3">
|
||||
@@ -103,7 +151,7 @@ const handleForgotPassword = async () => {
|
||||
</div>
|
||||
|
||||
<!-- Форма входа -->
|
||||
<n-card>
|
||||
<n-card class="auth-card" content-style="padding: 28px;">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
@@ -111,17 +159,18 @@ const handleForgotPassword = async () => {
|
||||
@submit.prevent="handleLogin"
|
||||
>
|
||||
<n-space vertical size="large">
|
||||
<!-- Email -->
|
||||
<!-- Логин -->
|
||||
<n-form-item label="Логин" path="login">
|
||||
<n-input
|
||||
id="login"
|
||||
v-model:value="form.login"
|
||||
placeholder="Ваш логин"
|
||||
size="large"
|
||||
round
|
||||
@keydown.enter="handleLogin"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon><TbUser /></n-icon>
|
||||
<n-icon :component="TbUser" depth="3" />
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
@@ -134,11 +183,12 @@ const handleForgotPassword = async () => {
|
||||
type="password"
|
||||
placeholder="Ваш пароль"
|
||||
size="large"
|
||||
round
|
||||
show-password-on="click"
|
||||
@keydown.enter="handleLogin"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon><TbLock /></n-icon>
|
||||
<n-icon :component="TbLock" depth="3" />
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
@@ -151,17 +201,21 @@ const handleForgotPassword = async () => {
|
||||
<!-- </n-form-item>-->
|
||||
|
||||
<!-- Ошибки -->
|
||||
<n-alert v-if="error" title="Ошибка входа" type="error">
|
||||
{{ error }}
|
||||
</n-alert>
|
||||
<Transition name="fade-slide">
|
||||
<n-alert v-if="error" title="Ошибка входа" type="error" closable @close="error = ''">
|
||||
{{ error }}
|
||||
</n-alert>
|
||||
</Transition>
|
||||
|
||||
<!-- Кнопка входа -->
|
||||
<n-button
|
||||
type="primary"
|
||||
size="large"
|
||||
round
|
||||
:loading="loading"
|
||||
@click="handleLogin"
|
||||
block
|
||||
class="auth-submit"
|
||||
>
|
||||
Войти в систему
|
||||
</n-button>
|
||||
@@ -195,6 +249,7 @@ const handleForgotPassword = async () => {
|
||||
<!-- Модальное окно восстановления пароля -->
|
||||
<n-modal v-model:show="showForgotPassword">
|
||||
<n-card
|
||||
class="auth-card"
|
||||
style="width: 400px"
|
||||
title="Восстановление пароля"
|
||||
:bordered="false"
|
||||
@@ -206,10 +261,11 @@ const handleForgotPassword = async () => {
|
||||
<n-input
|
||||
v-model:value="forgotForm.email"
|
||||
placeholder="Введите ваш email"
|
||||
round
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-button type="primary" block :loading="forgotLoading">
|
||||
<n-button type="primary" round block :loading="forgotLoading" @click="handleForgotPassword">
|
||||
Отправить инструкции
|
||||
</n-button>
|
||||
</n-space>
|
||||
@@ -222,5 +278,82 @@ const handleForgotPassword = async () => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.texture-layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background-color: currentColor;
|
||||
mask-repeat: repeat;
|
||||
-webkit-mask-repeat: repeat;
|
||||
mask-size: 666px 666px;
|
||||
-webkit-mask-size: 666px 666px;
|
||||
}
|
||||
|
||||
.texture-base {
|
||||
opacity: 0.04;
|
||||
mask-image: url('/assets/topography.svg');
|
||||
-webkit-mask-image: url('/assets/topography.svg');
|
||||
}
|
||||
|
||||
.texture-highlight {
|
||||
opacity: 0.18;
|
||||
mask-repeat: repeat, no-repeat;
|
||||
-webkit-mask-repeat: repeat, no-repeat;
|
||||
mask-size: 666px 666px, 100% 100%;
|
||||
-webkit-mask-size: 666px 666px, 100% 100%;
|
||||
mask-composite: intersect;
|
||||
-webkit-mask-composite: source-in;
|
||||
}
|
||||
|
||||
/* Логотип в градиентном круге со свечением */
|
||||
.auth-logo {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 9999px;
|
||||
background: linear-gradient(135deg, v-bind(primaryColor), v-bind(infoColor));
|
||||
box-shadow: 0 8px 28px -10px color-mix(in srgb, v-bind(primaryColor) 65%, transparent);
|
||||
}
|
||||
|
||||
/* Стеклянная карточка с лёгким размытием фона */
|
||||
.auth-card {
|
||||
background: color-mix(in srgb, v-bind(cardColor) 70%, transparent);
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
border: 1px solid color-mix(in srgb, v-bind(borderColor) 60%, transparent);
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 24px 60px -28px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.auth-submit {
|
||||
box-shadow: 0 10px 24px -10px color-mix(in srgb, v-bind(primaryColor) 60%, transparent);
|
||||
}
|
||||
|
||||
/* Плавное появление карточки входа */
|
||||
.auth-enter {
|
||||
animation: auth-enter 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes auth-enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(16px) scale(0.985);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Плавное появление блока ошибки */
|
||||
.fade-slide-enter-active,
|
||||
.fade-slide-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-slide-enter-from,
|
||||
.fade-slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user