perf: 登录界面重做
This commit is contained in:
@@ -1,14 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { EasyFlowFormSchema } from '@easyflow/common-ui';
|
import type {EasyFlowFormSchema} from '@easyflow/common-ui';
|
||||||
|
import {AuthenticationLogin, z} from '@easyflow/common-ui';
|
||||||
|
|
||||||
import { computed, onMounted } from 'vue';
|
import {computed, onMounted} from 'vue';
|
||||||
|
import {useAppConfig} from '@easyflow/hooks';
|
||||||
import { AuthenticationLogin, z } from '@easyflow/common-ui';
|
import {$t} from '@easyflow/locales';
|
||||||
import { useAppConfig } from '@easyflow/hooks';
|
import {useAuthStore} from '#/store';
|
||||||
import { $t } from '@easyflow/locales';
|
|
||||||
import { preferences } from '@easyflow/preferences';
|
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
|
||||||
|
|
||||||
defineOptions({ name: 'Login' });
|
defineOptions({ name: 'Login' });
|
||||||
onMounted(() => {});
|
onMounted(() => {});
|
||||||
@@ -17,8 +14,6 @@ const authStore = useAuthStore();
|
|||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
|
||||||
const title = computed(() => preferences.auth.welcomeBack);
|
|
||||||
const subTitle = computed(() => preferences.auth.loginSubtitle);
|
|
||||||
const formSchema = computed((): EasyFlowFormSchema[] => {
|
const formSchema = computed((): EasyFlowFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -91,23 +86,38 @@ function onSubmit(values: any) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="login-view">
|
||||||
<AuthenticationLogin
|
<AuthenticationLogin
|
||||||
:form-schema="formSchema"
|
:form-schema="formSchema"
|
||||||
:loading="authStore.loginLoading"
|
:loading="authStore.loginLoading"
|
||||||
:title="title"
|
:show-header="false"
|
||||||
:sub-title="subTitle"
|
|
||||||
@submit="onSubmit"
|
@submit="onSubmit"
|
||||||
/>
|
>
|
||||||
<div id="captcha-box" class="captcha-div"></div>
|
<template #overlay>
|
||||||
|
<div id="captcha-box" class="captcha-anchor"></div>
|
||||||
|
</template>
|
||||||
|
</AuthenticationLogin>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.captcha-div {
|
.login-view {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-anchor {
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 30vh;
|
z-index: 30;
|
||||||
left: 21vh;
|
}
|
||||||
|
|
||||||
|
.captcha-anchor:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-anchor :deep(*) {
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.platform-icon {
|
.platform-icon {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="auth-title mb-9 sm:mx-auto sm:w-full sm:max-w-md">
|
<div class="auth-title mb-8 w-full">
|
||||||
<h2
|
<h2
|
||||||
class="text-foreground mb-2.5 text-3xl font-semibold leading-[1.22] tracking-[-0.015em] lg:text-[2.15rem]"
|
class="text-foreground mb-2.5 text-[1.9rem] font-semibold leading-[1.12] tracking-[-0.03em] sm:text-[2.1rem]"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p class="text-muted-foreground text-[0.95rem] leading-7 lg:text-base">
|
<p class="text-muted-foreground max-w-[34rem] text-[0.97rem] leading-7 sm:text-[1rem]">
|
||||||
<slot name="desc"></slot>
|
<slot name="desc"></slot>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Recordable } from '@easyflow/types';
|
import type {Recordable} from '@easyflow/types';
|
||||||
|
|
||||||
import type { EasyFlowFormSchema } from '@easyflow-core/form-ui';
|
import type {EasyFlowFormSchema} from '@easyflow-core/form-ui';
|
||||||
|
import {useEasyFlowForm} from '@easyflow-core/form-ui';
|
||||||
|
|
||||||
import { computed, reactive } from 'vue';
|
import {computed, reactive} from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
|
|
||||||
import { $t } from '@easyflow/locales';
|
import {$t} from '@easyflow/locales';
|
||||||
|
import {EasyFlowButton} from '@easyflow-core/shadcn-ui';
|
||||||
import { useEasyFlowForm } from '@easyflow-core/form-ui';
|
|
||||||
import { EasyFlowButton } from '@easyflow-core/shadcn-ui';
|
|
||||||
|
|
||||||
import Title from './auth-title.vue';
|
import Title from './auth-title.vue';
|
||||||
|
|
||||||
@@ -89,10 +88,10 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="auth-code-login">
|
||||||
<Title>
|
<Title>
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
{{ title || $t('authentication.welcomeBack') }} 📲
|
{{ title || $t('authentication.welcomeBack') }}
|
||||||
</slot>
|
</slot>
|
||||||
<template #desc>
|
<template #desc>
|
||||||
<span class="text-muted-foreground">
|
<span class="text-muted-foreground">
|
||||||
@@ -102,13 +101,15 @@ defineExpose({
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</Title>
|
</Title>
|
||||||
<Form />
|
<div class="auth-form-group">
|
||||||
|
<Form />
|
||||||
|
</div>
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
:class="{
|
:class="{
|
||||||
'cursor-wait': loading,
|
'cursor-wait': loading,
|
||||||
}"
|
}"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
class="w-full"
|
class="mt-6 h-11 w-full rounded-xl text-base font-medium"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
>
|
>
|
||||||
<slot name="submitButtonText">
|
<slot name="submitButtonText">
|
||||||
@@ -117,7 +118,7 @@ defineExpose({
|
|||||||
</EasyFlowButton>
|
</EasyFlowButton>
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
v-if="showBack"
|
v-if="showBack"
|
||||||
class="mt-4 w-full"
|
class="mt-3 h-11 w-full rounded-xl"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="goToLogin()"
|
@click="goToLogin()"
|
||||||
>
|
>
|
||||||
@@ -125,3 +126,9 @@ defineExpose({
|
|||||||
</EasyFlowButton>
|
</EasyFlowButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.auth-form-group :deep(.easyflow-form-ui + .easyflow-form-ui) {
|
||||||
|
margin-top: 0.95rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { EasyFlowFormSchema } from '@easyflow-core/form-ui';
|
import type {EasyFlowFormSchema} from '@easyflow-core/form-ui';
|
||||||
|
import {useEasyFlowForm} from '@easyflow-core/form-ui';
|
||||||
|
|
||||||
import { computed, reactive } from 'vue';
|
import {computed, reactive} from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
|
|
||||||
import { $t } from '@easyflow/locales';
|
import {$t} from '@easyflow/locales';
|
||||||
|
import {EasyFlowButton} from '@easyflow-core/shadcn-ui';
|
||||||
import { useEasyFlowForm } from '@easyflow-core/form-ui';
|
|
||||||
import { EasyFlowButton } from '@easyflow-core/shadcn-ui';
|
|
||||||
|
|
||||||
import Title from './auth-title.vue';
|
import Title from './auth-title.vue';
|
||||||
|
|
||||||
@@ -82,10 +81,10 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="auth-forget-password">
|
||||||
<Title>
|
<Title>
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
{{ title || $t('authentication.forgetPassword') }} 🤦🏻♂️
|
{{ title || $t('authentication.forgetPassword') }}
|
||||||
</slot>
|
</slot>
|
||||||
<template #desc>
|
<template #desc>
|
||||||
<slot name="subTitle">
|
<slot name="subTitle">
|
||||||
@@ -93,7 +92,9 @@ defineExpose({
|
|||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
</Title>
|
</Title>
|
||||||
<Form />
|
<div class="auth-form-group">
|
||||||
|
<Form />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
@@ -101,16 +102,26 @@ defineExpose({
|
|||||||
'cursor-wait': loading,
|
'cursor-wait': loading,
|
||||||
}"
|
}"
|
||||||
aria-label="submit"
|
aria-label="submit"
|
||||||
class="mt-2 w-full"
|
class="mt-6 h-11 w-full rounded-xl text-base font-medium"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
>
|
>
|
||||||
<slot name="submitButtonText">
|
<slot name="submitButtonText">
|
||||||
{{ submitButtonText || $t('authentication.sendResetLink') }}
|
{{ submitButtonText || $t('authentication.sendResetLink') }}
|
||||||
</slot>
|
</slot>
|
||||||
</EasyFlowButton>
|
</EasyFlowButton>
|
||||||
<EasyFlowButton class="mt-4 w-full" variant="outline" @click="goToLogin()">
|
<EasyFlowButton
|
||||||
|
class="mt-3 h-11 w-full rounded-xl"
|
||||||
|
variant="outline"
|
||||||
|
@click="goToLogin()"
|
||||||
|
>
|
||||||
{{ $t('common.back') }}
|
{{ $t('common.back') }}
|
||||||
</EasyFlowButton>
|
</EasyFlowButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.auth-form-group :deep(.easyflow-form-ui + .easyflow-form-ui) {
|
||||||
|
margin-top: 0.95rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Recordable } from '@easyflow/types';
|
import type {Recordable} from '@easyflow/types';
|
||||||
|
|
||||||
import type { EasyFlowFormSchema } from '@easyflow-core/form-ui';
|
import type {EasyFlowFormSchema} from '@easyflow-core/form-ui';
|
||||||
|
import {useEasyFlowForm} from '@easyflow-core/form-ui';
|
||||||
|
|
||||||
import type { AuthenticationProps } from './types';
|
import type {AuthenticationProps} from './types';
|
||||||
|
|
||||||
import { computed, onMounted, reactive, ref } from 'vue';
|
import {computed, onMounted, reactive, ref} from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
|
|
||||||
import { $t } from '@easyflow/locales';
|
import {$t} from '@easyflow/locales';
|
||||||
|
import {EasyFlowButton, EasyFlowCheckbox} from '@easyflow-core/shadcn-ui';
|
||||||
import { useEasyFlowForm } from '@easyflow-core/form-ui';
|
|
||||||
import { EasyFlowButton, EasyFlowCheckbox } from '@easyflow-core/shadcn-ui';
|
|
||||||
|
|
||||||
import Title from './auth-title.vue';
|
import Title from './auth-title.vue';
|
||||||
import ThirdPartyLogin from './third-party-login.vue';
|
import ThirdPartyLogin from './third-party-login.vue';
|
||||||
@@ -33,6 +32,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
registerPath: '/auth/register',
|
registerPath: '/auth/register',
|
||||||
showCodeLogin: false,
|
showCodeLogin: false,
|
||||||
showForgetPassword: false,
|
showForgetPassword: false,
|
||||||
|
showHeader: true,
|
||||||
showQrcodeLogin: false,
|
showQrcodeLogin: false,
|
||||||
showRegister: false,
|
showRegister: false,
|
||||||
showRememberMe: false,
|
showRememberMe: false,
|
||||||
@@ -58,11 +58,9 @@ const [Form, formApi] = useEasyFlowForm(
|
|||||||
);
|
);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const REMEMBER_ME_KEY = `REMEMBER_ME_USERNAME_${location.hostname}`;
|
const REMEMBER_ME_KEY = `REMEMBER_ME_ACCOUNT_${location.hostname}`;
|
||||||
|
const localAccount = localStorage.getItem(REMEMBER_ME_KEY) || '';
|
||||||
const localUsername = localStorage.getItem(REMEMBER_ME_KEY) || '';
|
const rememberMe = ref(!!localAccount);
|
||||||
|
|
||||||
const rememberMe = ref(!!localUsername);
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
@@ -70,7 +68,7 @@ async function handleSubmit() {
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
REMEMBER_ME_KEY,
|
REMEMBER_ME_KEY,
|
||||||
rememberMe.value ? values?.username : '',
|
rememberMe.value ? (values?.account ?? '') : '',
|
||||||
);
|
);
|
||||||
emit('submit', values);
|
emit('submit', values);
|
||||||
}
|
}
|
||||||
@@ -81,8 +79,8 @@ function handleGo(path: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (localUsername) {
|
if (localAccount) {
|
||||||
formApi.setFieldValue('username', localUsername);
|
formApi.setFieldValue('account', localAccount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -93,65 +91,69 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="auth-login" @keydown.enter.prevent="handleSubmit">
|
<div class="auth-login" @keydown.enter.prevent="handleSubmit">
|
||||||
<slot name="title">
|
<template v-if="showHeader">
|
||||||
<Title>
|
<slot name="title">
|
||||||
<slot name="title">
|
<Title>
|
||||||
{{ title || $t('authentication.welcomeBack') }}
|
<slot name="title">
|
||||||
</slot>
|
{{ title || $t('authentication.welcomeBack') }}
|
||||||
<template #desc>
|
</slot>
|
||||||
<span class="text-muted-foreground">
|
<template v-if="subTitle || $t('authentication.loginSubtitle')" #desc>
|
||||||
<slot name="subTitle">
|
<span class="text-muted-foreground">
|
||||||
{{ subTitle || $t('authentication.loginSubtitle') }}
|
<slot name="subTitle">
|
||||||
</slot>
|
{{ subTitle || $t('authentication.loginSubtitle') }}
|
||||||
</span>
|
</slot>
|
||||||
</template>
|
</span>
|
||||||
</Title>
|
</template>
|
||||||
</slot>
|
</Title>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
<Form />
|
<div class="auth-form-group">
|
||||||
|
<Form />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="showRememberMe || showForgetPassword"
|
v-if="showRememberMe || showForgetPassword"
|
||||||
class="auth-login-options mb-7 mt-2 flex justify-between"
|
class="auth-login-options mt-1 flex items-center justify-between gap-3"
|
||||||
>
|
>
|
||||||
<div class="flex-center">
|
<EasyFlowCheckbox
|
||||||
<EasyFlowCheckbox
|
v-if="showRememberMe"
|
||||||
v-if="showRememberMe"
|
v-model="rememberMe"
|
||||||
v-model="rememberMe"
|
class="auth-checkbox"
|
||||||
name="rememberMe"
|
name="rememberMe"
|
||||||
>
|
>
|
||||||
{{ $t('authentication.rememberMe') }}
|
{{ $t('authentication.rememberMe') }}
|
||||||
</EasyFlowCheckbox>
|
</EasyFlowCheckbox>
|
||||||
</div>
|
|
||||||
|
|
||||||
<span
|
<button
|
||||||
v-if="showForgetPassword"
|
v-if="showForgetPassword"
|
||||||
class="easyflow-link text-sm font-normal"
|
class="auth-inline-action"
|
||||||
|
type="button"
|
||||||
@click="handleGo(forgetPasswordPath)"
|
@click="handleGo(forgetPasswordPath)"
|
||||||
>
|
>
|
||||||
{{ $t('authentication.forgetPassword') }}
|
{{ $t('authentication.forgetPassword') }}
|
||||||
</span>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
:class="{
|
:class="{ 'cursor-wait': loading }"
|
||||||
'cursor-wait': loading,
|
|
||||||
}"
|
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
aria-label="login"
|
aria-label="login"
|
||||||
class="auth-submit-button h-11 w-full rounded-xl text-base font-medium"
|
class="auth-submit-button auth-brand-submit mt-6 h-11 w-full rounded-xl text-base font-medium"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
>
|
>
|
||||||
{{ submitButtonText || $t('common.login') }}
|
{{ submitButtonText || $t('common.login') }}
|
||||||
</EasyFlowButton>
|
</EasyFlowButton>
|
||||||
|
|
||||||
|
<slot name="overlay"></slot>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="showCodeLogin || showQrcodeLogin"
|
v-if="showCodeLogin || showQrcodeLogin"
|
||||||
class="auth-login-quick mb-2 mt-5 flex items-center justify-between"
|
class="auth-alt-modes mt-5 grid gap-3 sm:grid-cols-2"
|
||||||
>
|
>
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
v-if="showCodeLogin"
|
v-if="showCodeLogin"
|
||||||
class="w-1/2"
|
class="auth-secondary-button h-11 w-full rounded-xl"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="handleGo(codeLoginPath)"
|
@click="handleGo(codeLoginPath)"
|
||||||
>
|
>
|
||||||
@@ -159,7 +161,7 @@ defineExpose({
|
|||||||
</EasyFlowButton>
|
</EasyFlowButton>
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
v-if="showQrcodeLogin"
|
v-if="showQrcodeLogin"
|
||||||
class="ml-4 w-1/2"
|
class="auth-secondary-button h-11 w-full rounded-xl"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="handleGo(qrCodeLoginPath)"
|
@click="handleGo(qrCodeLoginPath)"
|
||||||
>
|
>
|
||||||
@@ -172,21 +174,113 @@ defineExpose({
|
|||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<slot name="to-register">
|
<slot name="to-register">
|
||||||
<div v-if="showRegister" class="mt-4 text-center text-sm">
|
<div v-if="showRegister" class="auth-footer-copy mt-5 text-center text-sm">
|
||||||
{{ $t('authentication.accountTip') }}
|
{{ $t('authentication.accountTip') }}
|
||||||
<span
|
<button
|
||||||
class="easyflow-link text-sm font-normal"
|
class="auth-inline-action"
|
||||||
|
type="button"
|
||||||
@click="handleGo(registerPath)"
|
@click="handleGo(registerPath)"
|
||||||
>
|
>
|
||||||
{{ $t('authentication.createAccount') }}
|
{{ $t('authentication.createAccount') }}
|
||||||
</span>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.auth-login :deep(.easyflow-form-ui + .easyflow-form-ui) {
|
.auth-login {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-form-group :deep(.easyflow-form-ui + .easyflow-form-ui) {
|
||||||
margin-top: 0.95rem;
|
margin-top: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-form-group :deep(.easyflow-form-ui .text-destructive) {
|
||||||
|
font-size: 0.84rem;
|
||||||
|
margin-top: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-login-options {
|
||||||
|
min-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-checkbox {
|
||||||
|
color: hsl(var(--text-muted));
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-inline-action {
|
||||||
|
color: hsl(var(--nav-item-active-foreground));
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: opacity 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-inline-action:hover {
|
||||||
|
opacity: 0.76;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-secondary-button {
|
||||||
|
background: hsl(var(--surface-elevated));
|
||||||
|
border-color: hsl(var(--line-subtle));
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-brand-submit {
|
||||||
|
background:
|
||||||
|
linear-gradient(
|
||||||
|
120deg,
|
||||||
|
rgb(11 111 211) 0%,
|
||||||
|
rgb(22 159 200) 38%,
|
||||||
|
rgb(38 199 193) 62%,
|
||||||
|
rgb(11 111 211) 100%
|
||||||
|
);
|
||||||
|
background-size: 200% 200%;
|
||||||
|
border: none;
|
||||||
|
box-shadow:
|
||||||
|
0 22px 34px -22px rgb(11 111 211 / 0.56),
|
||||||
|
inset 0 1px 0 rgb(255 255 255 / 0.24);
|
||||||
|
color: rgb(255 255 255);
|
||||||
|
transition:
|
||||||
|
transform 180ms ease,
|
||||||
|
box-shadow 180ms ease,
|
||||||
|
filter 180ms ease;
|
||||||
|
animation: auth-brand-gradient 6s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-brand-submit:hover {
|
||||||
|
box-shadow:
|
||||||
|
0 24px 38px -22px rgb(11 111 211 / 0.62),
|
||||||
|
inset 0 1px 0 rgb(255 255 255 / 0.28);
|
||||||
|
filter: saturate(1.04);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-brand-submit:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-brand-submit:focus-visible {
|
||||||
|
outline: 2px solid rgb(78 176 255 / 0.8);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-footer-copy {
|
||||||
|
color: hsl(var(--text-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes auth-brand-gradient {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import {ref} from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
|
|
||||||
import { $t } from '@easyflow/locales';
|
import {$t} from '@easyflow/locales';
|
||||||
|
|
||||||
import { EasyFlowButton } from '@easyflow-core/shadcn-ui';
|
import {EasyFlowButton} from '@easyflow-core/shadcn-ui';
|
||||||
|
|
||||||
import { useQRCode } from '@vueuse/integrations/useQRCode';
|
import {useQRCode} from '@vueuse/integrations/useQRCode';
|
||||||
|
|
||||||
import Title from './auth-title.vue';
|
import Title from './auth-title.vue';
|
||||||
|
|
||||||
@@ -70,10 +70,10 @@ function goToLogin() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="auth-qrcode-login">
|
||||||
<Title>
|
<Title>
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
{{ title || $t('authentication.welcomeBack') }} 📱
|
{{ title || $t('authentication.welcomeBack') }}
|
||||||
</slot>
|
</slot>
|
||||||
<template #desc>
|
<template #desc>
|
||||||
<span class="text-muted-foreground">
|
<span class="text-muted-foreground">
|
||||||
@@ -84,9 +84,11 @@ function goToLogin() {
|
|||||||
</template>
|
</template>
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<div class="flex-col-center mt-6">
|
<div class="auth-qrcode-panel mt-6">
|
||||||
<img :src="qrcode" alt="qrcode" class="w-1/2" />
|
<div class="auth-qrcode-frame">
|
||||||
<p class="text-muted-foreground mt-4 text-sm">
|
<img :src="qrcode" alt="qrcode" class="auth-qrcode-image" />
|
||||||
|
</div>
|
||||||
|
<p class="text-muted-foreground mt-4 text-sm leading-6">
|
||||||
<slot name="description">
|
<slot name="description">
|
||||||
{{ description || $t('authentication.qrcodePrompt') }}
|
{{ description || $t('authentication.qrcodePrompt') }}
|
||||||
</slot>
|
</slot>
|
||||||
@@ -95,7 +97,7 @@ function goToLogin() {
|
|||||||
|
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
v-if="showBack"
|
v-if="showBack"
|
||||||
class="mt-4 w-full"
|
class="mt-6 h-11 w-full rounded-xl"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="goToLogin()"
|
@click="goToLogin()"
|
||||||
>
|
>
|
||||||
@@ -103,3 +105,32 @@ function goToLogin() {
|
|||||||
</EasyFlowButton>
|
</EasyFlowButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.auth-qrcode-panel {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-qrcode-frame {
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(180deg, rgb(255 255 255 / 0.92), rgb(244 249 255 / 0.96));
|
||||||
|
border: 1px solid hsl(var(--line-subtle));
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.6);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 17rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-qrcode-image {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: 1rem;
|
||||||
|
width: min(100%, 13rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .auth-qrcode-frame {
|
||||||
|
background: linear-gradient(180deg, rgb(14 22 36 / 0.92), rgb(11 19 31 / 0.96));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Recordable } from '@easyflow/types';
|
import type {Recordable} from '@easyflow/types';
|
||||||
|
|
||||||
import type { EasyFlowFormSchema } from '@easyflow-core/form-ui';
|
import type {EasyFlowFormSchema} from '@easyflow-core/form-ui';
|
||||||
|
import {useEasyFlowForm} from '@easyflow-core/form-ui';
|
||||||
|
|
||||||
import { computed, reactive } from 'vue';
|
import {computed, reactive} from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
|
|
||||||
import { $t } from '@easyflow/locales';
|
import {$t} from '@easyflow/locales';
|
||||||
|
import {EasyFlowButton} from '@easyflow-core/shadcn-ui';
|
||||||
import { useEasyFlowForm } from '@easyflow-core/form-ui';
|
|
||||||
import { EasyFlowButton } from '@easyflow-core/shadcn-ui';
|
|
||||||
|
|
||||||
import Title from './auth-title.vue';
|
import Title from './auth-title.vue';
|
||||||
|
|
||||||
@@ -85,10 +84,10 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="auth-register">
|
||||||
<Title>
|
<Title>
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
{{ title || $t('authentication.createAnAccount') }} 🚀
|
{{ title || $t('authentication.createAnAccount') }}
|
||||||
</slot>
|
</slot>
|
||||||
<template #desc>
|
<template #desc>
|
||||||
<slot name="subTitle">
|
<slot name="subTitle">
|
||||||
@@ -96,7 +95,9 @@ defineExpose({
|
|||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
</Title>
|
</Title>
|
||||||
<Form />
|
<div class="auth-form-group">
|
||||||
|
<Form />
|
||||||
|
</div>
|
||||||
|
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
:class="{
|
:class="{
|
||||||
@@ -104,18 +105,35 @@ defineExpose({
|
|||||||
}"
|
}"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
aria-label="register"
|
aria-label="register"
|
||||||
class="mt-2 w-full"
|
class="mt-6 h-11 w-full rounded-xl text-base font-medium"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
>
|
>
|
||||||
<slot name="submitButtonText">
|
<slot name="submitButtonText">
|
||||||
{{ submitButtonText || $t('authentication.signUp') }}
|
{{ submitButtonText || $t('authentication.signUp') }}
|
||||||
</slot>
|
</slot>
|
||||||
</EasyFlowButton>
|
</EasyFlowButton>
|
||||||
<div class="mt-4 text-center text-sm">
|
<div class="mt-5 text-center text-sm text-[hsl(var(--text-muted))]">
|
||||||
{{ $t('authentication.alreadyHaveAccount') }}
|
{{ $t('authentication.alreadyHaveAccount') }}
|
||||||
<span class="easyflow-link text-sm font-normal" @click="goToLogin()">
|
<button class="auth-inline-action" type="button" @click="goToLogin()">
|
||||||
{{ $t('authentication.goToLogin') }}
|
{{ $t('authentication.goToLogin') }}
|
||||||
</span>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.auth-form-group :deep(.easyflow-form-ui + .easyflow-form-ui) {
|
||||||
|
margin-top: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-inline-action {
|
||||||
|
color: hsl(var(--nav-item-active-foreground));
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: opacity 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-inline-action:hover {
|
||||||
|
opacity: 0.76;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ interface AuthenticationProps {
|
|||||||
*/
|
*/
|
||||||
showThirdPartyLogin?: boolean;
|
showThirdPartyLogin?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @zh_CN 是否显示登录头部
|
||||||
|
*/
|
||||||
|
showHeader?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 登录框子标题
|
* @zh_CN 登录框子标题
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ToolbarType } from './types';
|
import type {ToolbarType} from './types';
|
||||||
|
|
||||||
import { computed, ref, watch } from 'vue';
|
import {computed, onBeforeUnmount, onMounted, ref} from 'vue';
|
||||||
|
import {useRoute} from 'vue-router';
|
||||||
|
|
||||||
import { preferences, usePreferences } from '@easyflow/preferences';
|
import {$t} from '@easyflow/locales';
|
||||||
|
import {preferences, usePreferences} from '@easyflow/preferences';
|
||||||
|
|
||||||
import { Copyright } from '../basic/copyright';
|
import {Copyright} from '../basic/copyright';
|
||||||
import AuthenticationFormView from './form.vue';
|
import AuthenticationFormView from './form.vue';
|
||||||
import SloganIcon from './icons/slogan.vue';
|
|
||||||
import Toolbar from './toolbar.vue';
|
import Toolbar from './toolbar.vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -16,7 +17,6 @@ interface Props {
|
|||||||
logoDark?: string;
|
logoDark?: string;
|
||||||
pageTitle?: string;
|
pageTitle?: string;
|
||||||
pageDescription?: string;
|
pageDescription?: string;
|
||||||
sloganImage?: string;
|
|
||||||
toolbar?: boolean;
|
toolbar?: boolean;
|
||||||
copyright?: boolean;
|
copyright?: boolean;
|
||||||
toolbarList?: ToolbarType[];
|
toolbarList?: ToolbarType[];
|
||||||
@@ -30,20 +30,14 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
logoDark: '',
|
logoDark: '',
|
||||||
pageDescription: '',
|
pageDescription: '',
|
||||||
pageTitle: '',
|
pageTitle: '',
|
||||||
sloganImage: '',
|
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
toolbarList: () => ['color', 'language', 'layout', 'theme'],
|
toolbarList: () => ['language', 'theme'],
|
||||||
clickLogo: () => {},
|
clickLogo: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
|
const { isDark } = usePreferences();
|
||||||
usePreferences();
|
const route = useRoute();
|
||||||
|
|
||||||
const isSloganLoadError = ref(false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @zh_CN 根据主题选择合适的 logo 图标
|
|
||||||
*/
|
|
||||||
const logoSrc = computed(() => {
|
const logoSrc = computed(() => {
|
||||||
if (isDark.value && props.logoDark) {
|
if (isDark.value && props.logoDark) {
|
||||||
return props.logoDark;
|
return props.logoDark;
|
||||||
@@ -51,272 +45,338 @@ const logoSrc = computed(() => {
|
|||||||
return props.logo;
|
return props.logo;
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
const activeCapabilityIndex = ref(0);
|
||||||
() => props.sloganImage,
|
let capabilityTimer: null | number = null;
|
||||||
() => {
|
const currentHour = ref(new Date().getHours());
|
||||||
isSloganLoadError.value = false;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleSloganError() {
|
const isLoginRoute = computed(() => route.path === '/auth/login');
|
||||||
isSloganLoadError.value = true;
|
const capabilityLabels = computed(() => [
|
||||||
}
|
$t('authentication.capabilityModel'),
|
||||||
|
$t('authentication.capabilityAgent'),
|
||||||
|
$t('authentication.capabilityWorkflow'),
|
||||||
|
$t('authentication.capabilityKnowledge'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const stageGreeting = computed(() => {
|
||||||
|
if (currentHour.value >= 5 && currentHour.value < 11) {
|
||||||
|
return $t('authentication.greetingMorning');
|
||||||
|
}
|
||||||
|
if (currentHour.value >= 11 && currentHour.value < 14) {
|
||||||
|
return $t('authentication.greetingNoon');
|
||||||
|
}
|
||||||
|
if (currentHour.value >= 14 && currentHour.value < 18) {
|
||||||
|
return $t('authentication.greetingAfternoon');
|
||||||
|
}
|
||||||
|
return $t('authentication.greetingEvening');
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
capabilityTimer = window.setInterval(() => {
|
||||||
|
activeCapabilityIndex.value =
|
||||||
|
(activeCapabilityIndex.value + 1) % capabilityLabels.value.length;
|
||||||
|
}, 2400);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (capabilityTimer) {
|
||||||
|
clearInterval(capabilityTimer);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="[isDark ? 'dark' : '']"
|
:class="[isDark ? 'dark' : '']"
|
||||||
class="auth-shell relative flex min-h-full flex-1 select-none overflow-x-hidden"
|
class="auth-shell relative flex min-h-full flex-1 select-none overflow-hidden"
|
||||||
>
|
>
|
||||||
|
<div class="auth-shell-grid absolute inset-0"></div>
|
||||||
|
<div class="auth-shell-noise absolute inset-0"></div>
|
||||||
|
<div class="auth-glow auth-glow-primary"></div>
|
||||||
|
<div class="auth-glow auth-glow-secondary"></div>
|
||||||
|
<div class="auth-glow auth-glow-tertiary"></div>
|
||||||
|
|
||||||
<template v-if="toolbar">
|
<template v-if="toolbar">
|
||||||
<slot name="toolbar">
|
<slot name="toolbar">
|
||||||
<Toolbar :toolbar-list="toolbarList" />
|
<Toolbar :toolbar-list="toolbarList" />
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<AuthenticationFormView
|
|
||||||
v-if="authPanelLeft"
|
|
||||||
class="auth-panel-form min-h-full w-full flex-1 lg:w-[42%] lg:flex-initial"
|
|
||||||
data-side="left"
|
|
||||||
>
|
|
||||||
<template v-if="copyright" #copyright>
|
|
||||||
<slot name="copyright">
|
|
||||||
<Copyright
|
|
||||||
v-if="preferences.copyright.enable"
|
|
||||||
v-bind="preferences.copyright"
|
|
||||||
/>
|
|
||||||
</slot>
|
|
||||||
</template>
|
|
||||||
</AuthenticationFormView>
|
|
||||||
|
|
||||||
<slot name="logo">
|
<slot name="logo">
|
||||||
<div
|
<div
|
||||||
v-if="logoSrc || appName"
|
v-if="logoSrc || appName"
|
||||||
class="absolute left-0 top-0 z-20 flex flex-1"
|
class="absolute left-0 top-0 z-20 flex flex-1"
|
||||||
@click="clickLogo"
|
@click="clickLogo"
|
||||||
>
|
>
|
||||||
<div
|
<div class="auth-brand-chip text-foreground ml-4 mt-4 flex items-center sm:ml-6 sm:mt-6">
|
||||||
class="text-foreground ml-4 mt-4 flex flex-1 items-center sm:left-6 sm:top-6"
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
v-if="logoSrc"
|
v-if="logoSrc"
|
||||||
:key="logoSrc"
|
:key="logoSrc"
|
||||||
:alt="appName"
|
:alt="appName"
|
||||||
:src="logoSrc"
|
:src="logoSrc"
|
||||||
class="mr-2"
|
class="mr-2.5"
|
||||||
width="120"
|
width="112"
|
||||||
/>
|
/>
|
||||||
|
<span v-else class="auth-brand-name">{{ appName }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<div
|
<main class="auth-stage relative z-10 flex min-h-full w-full items-center justify-center px-6 pb-10 pt-28 sm:px-10 sm:pt-32">
|
||||||
v-if="!authPanelCenter"
|
<div class="auth-stage-inner mx-auto flex w-full max-w-[1080px] flex-col items-center">
|
||||||
class="auth-hero relative hidden min-h-full w-0 flex-1 overflow-hidden lg:block"
|
<div
|
||||||
>
|
v-if="isLoginRoute"
|
||||||
<div class="auth-hero-base absolute inset-0"></div>
|
class="auth-stage-copy w-full max-w-[780px] text-center"
|
||||||
<div class="auth-orb auth-orb-left"></div>
|
>
|
||||||
<div class="auth-orb auth-orb-right"></div>
|
<h1 class="auth-page-title text-foreground">
|
||||||
<div class="auth-orb auth-orb-bottom"></div>
|
{{ stageGreeting }}
|
||||||
|
</h1>
|
||||||
|
<div class="auth-stage-switcher text-muted-foreground" aria-label="同一入口能力切换">
|
||||||
|
<span class="auth-stage-switcher-label">在同一入口管理</span>
|
||||||
|
<span class="auth-stage-pill" aria-live="polite">
|
||||||
|
<Transition mode="out-in" name="auth-pill">
|
||||||
|
<span
|
||||||
|
:key="capabilityLabels[activeCapabilityIndex]"
|
||||||
|
class="auth-stage-pill-text"
|
||||||
|
>
|
||||||
|
{{ capabilityLabels[activeCapabilityIndex] }}
|
||||||
|
</span>
|
||||||
|
</Transition>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<AuthenticationFormView
|
||||||
:key="authPanelLeft ? 'left' : authPanelRight ? 'right' : 'center'"
|
:class="[
|
||||||
class="auth-hero-content flex-col-center relative h-full px-12"
|
'auth-window-host w-full',
|
||||||
:class="{
|
isLoginRoute ? 'max-w-[25rem] sm:max-w-[26rem]' : 'max-w-[31rem]',
|
||||||
'enter-x': authPanelLeft,
|
isLoginRoute ? 'mt-8 sm:mt-10' : 'mt-0 sm:mt-4',
|
||||||
'-enter-x': authPanelRight,
|
]"
|
||||||
}"
|
data-side="bottom"
|
||||||
>
|
>
|
||||||
<div class="auth-hero-visual">
|
<template v-if="copyright" #copyright>
|
||||||
<template v-if="sloganImage && !isSloganLoadError">
|
<slot name="copyright">
|
||||||
<img
|
<Copyright
|
||||||
:alt="appName"
|
v-if="preferences.copyright.enable"
|
||||||
:src="sloganImage"
|
v-bind="preferences.copyright"
|
||||||
class="auth-hero-image"
|
/>
|
||||||
@error="handleSloganError"
|
</slot>
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<SloganIcon v-else :alt="appName" class="auth-hero-fallback" />
|
</AuthenticationFormView>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="auth-page-title text-foreground">
|
|
||||||
{{ pageTitle }}
|
|
||||||
</div>
|
|
||||||
<div class="auth-page-desc text-muted-foreground">
|
|
||||||
{{ pageDescription }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<div v-if="authPanelCenter" class="auth-center relative w-full">
|
|
||||||
<div class="auth-hero-base absolute inset-0"></div>
|
|
||||||
<div class="auth-orb auth-orb-left"></div>
|
|
||||||
<div class="auth-orb auth-orb-right"></div>
|
|
||||||
<AuthenticationFormView
|
|
||||||
class="auth-center-card shadow-float w-full rounded-3xl pb-20 md:w-2/3 lg:w-1/2 xl:w-[36%]"
|
|
||||||
data-side="bottom"
|
|
||||||
>
|
|
||||||
<template v-if="copyright" #copyright>
|
|
||||||
<slot name="copyright">
|
|
||||||
<Copyright
|
|
||||||
v-if="preferences.copyright.enable"
|
|
||||||
v-bind="preferences.copyright"
|
|
||||||
/>
|
|
||||||
</slot>
|
|
||||||
</template>
|
|
||||||
</AuthenticationFormView>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AuthenticationFormView
|
|
||||||
v-if="authPanelRight"
|
|
||||||
class="auth-panel-form min-h-full w-full flex-1 lg:w-[42%] lg:flex-initial"
|
|
||||||
data-side="right"
|
|
||||||
>
|
|
||||||
<template v-if="copyright" #copyright>
|
|
||||||
<slot name="copyright">
|
|
||||||
<Copyright
|
|
||||||
v-if="preferences.copyright.enable"
|
|
||||||
v-bind="preferences.copyright"
|
|
||||||
/>
|
|
||||||
</slot>
|
|
||||||
</template>
|
|
||||||
</AuthenticationFormView>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.auth-shell {
|
.auth-shell {
|
||||||
background: linear-gradient(180deg, #f8fbff 0%, #f1f6ff 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-panel-form {
|
|
||||||
backdrop-filter: blur(6px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-hero {
|
|
||||||
border-inline: 1px solid rgb(29 108 255 / 6%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-hero-base {
|
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at 22% 18%, rgb(56 131 255 / 14%) 0, transparent 42%),
|
radial-gradient(circle at top, rgb(255 255 255 / 78%), rgb(255 255 255 / 0) 36%),
|
||||||
radial-gradient(circle at 82% 16%, rgb(88 179 255 / 12%) 0, transparent 35%),
|
linear-gradient(180deg, #f7faff 0%, #eef4fd 55%, #edf3fb 100%);
|
||||||
radial-gradient(circle at 70% 86%, rgb(112 146 255 / 10%) 0, transparent 40%),
|
|
||||||
linear-gradient(145deg, #eef5ff 0%, #f4f8ff 45%, #eef4ff 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-hero-content {
|
.auth-shell-grid {
|
||||||
z-index: 2;
|
background-image:
|
||||||
|
linear-gradient(rgb(13 74 160 / 0.06) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgb(13 74 160 / 0.06) 1px, transparent 1px);
|
||||||
|
background-position: center center;
|
||||||
|
background-size: 120px 120px;
|
||||||
|
mask-image: linear-gradient(180deg, rgb(0 0 0 / 0.28), transparent 75%);
|
||||||
|
opacity: 0.42;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-hero-visual {
|
.auth-shell-noise {
|
||||||
width: min(860px, 90%);
|
background-image:
|
||||||
max-width: 920px;
|
radial-gradient(circle at 20% 20%, rgb(255 255 255 / 0.35) 0 0.9px, transparent 1.2px),
|
||||||
|
radial-gradient(circle at 80% 30%, rgb(255 255 255 / 0.22) 0 1px, transparent 1.3px),
|
||||||
|
radial-gradient(circle at 40% 70%, rgb(11 111 211 / 0.08) 0 1px, transparent 1.4px);
|
||||||
|
background-size: 180px 180px, 240px 240px, 200px 200px;
|
||||||
|
mix-blend-mode: soft-light;
|
||||||
|
opacity: 0.65;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-hero-image {
|
.auth-glow {
|
||||||
width: 100%;
|
border-radius: 9999px;
|
||||||
height: auto;
|
pointer-events: none;
|
||||||
object-fit: contain;
|
position: absolute;
|
||||||
filter: drop-shadow(0 24px 48px rgb(24 78 173 / 10%));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-hero-fallback {
|
.auth-glow-primary {
|
||||||
width: 100%;
|
background: radial-gradient(circle, rgb(87 150 255 / 0.24) 0%, rgb(87 150 255 / 0) 68%);
|
||||||
height: auto;
|
height: 26rem;
|
||||||
|
left: 50%;
|
||||||
|
top: 4rem;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 26rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-glow-secondary {
|
||||||
|
background: radial-gradient(circle, rgb(36 189 211 / 0.18) 0%, rgb(36 189 211 / 0) 72%);
|
||||||
|
height: 20rem;
|
||||||
|
left: 14%;
|
||||||
|
top: 46%;
|
||||||
|
width: 20rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-glow-tertiary {
|
||||||
|
background: radial-gradient(circle, rgb(66 116 255 / 0.14) 0%, rgb(66 116 255 / 0) 74%);
|
||||||
|
bottom: 8%;
|
||||||
|
height: 22rem;
|
||||||
|
right: 10%;
|
||||||
|
width: 22rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-brand-chip {
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
background: rgb(255 255 255 / 0.72);
|
||||||
|
border: 1px solid rgb(255 255 255 / 0.84);
|
||||||
|
border-radius: 9999px;
|
||||||
|
box-shadow: 0 20px 44px -32px rgb(11 59 132 / 0.26);
|
||||||
|
min-height: 3rem;
|
||||||
|
padding: 0.45rem 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-brand-name {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-page-title {
|
.auth-page-title {
|
||||||
margin-top: 1.2rem;
|
font-size: clamp(2.1rem, 4vw, 3.7rem);
|
||||||
font-size: clamp(1.9rem, 2.5vw, 2.45rem);
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.04em;
|
||||||
text-align: center;
|
line-height: 1.05;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 16ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-page-desc {
|
.auth-stage-switcher {
|
||||||
margin-top: 0.9rem;
|
|
||||||
max-width: 42rem;
|
|
||||||
font-size: clamp(0.96rem, 1.1vw, 1.12rem);
|
|
||||||
line-height: 1.7;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-center {
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.7rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-center-card {
|
.auth-stage-switcher-label {
|
||||||
position: relative;
|
font-size: 0.98rem;
|
||||||
z-index: 2;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-orb {
|
.auth-stage-pill {
|
||||||
|
align-items: center;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgb(255 255 255 / 0.74);
|
||||||
|
border: 1px solid rgb(255 255 255 / 0.9);
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
position: absolute;
|
box-shadow: 0 18px 34px -28px rgb(14 61 132 / 0.3);
|
||||||
|
color: hsl(var(--nav-item-active-foreground));
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 6.5rem;
|
||||||
|
padding: 0.55rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-stage-pill-text {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-pill-enter-active,
|
||||||
|
.auth-pill-leave-active {
|
||||||
|
transition:
|
||||||
|
opacity 180ms ease,
|
||||||
|
transform 180ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-pill-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-pill-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-window-host {
|
||||||
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-orb-left {
|
|
||||||
background: rgb(101 149 255 / 18%);
|
|
||||||
height: 18rem;
|
|
||||||
left: -4rem;
|
|
||||||
top: 4.5rem;
|
|
||||||
width: 18rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-orb-right {
|
|
||||||
background: rgb(124 188 255 / 16%);
|
|
||||||
height: 10rem;
|
|
||||||
right: 3rem;
|
|
||||||
top: 5rem;
|
|
||||||
width: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-orb-bottom {
|
|
||||||
background: rgb(93 154 255 / 12%);
|
|
||||||
bottom: 2rem;
|
|
||||||
height: 14rem;
|
|
||||||
right: 5rem;
|
|
||||||
width: 14rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark.auth-shell {
|
.dark.auth-shell {
|
||||||
background: linear-gradient(180deg, #05080f 0%, #070b14 100%);
|
background:
|
||||||
|
radial-gradient(circle at top, rgb(35 66 114 / 0.32), rgb(15 22 35 / 0) 40%),
|
||||||
|
linear-gradient(180deg, #06101b 0%, #08111d 52%, #09131f 100%);
|
||||||
|
|
||||||
.auth-hero {
|
.auth-shell-grid {
|
||||||
border-inline: 1px solid rgb(125 168 255 / 10%);
|
background-image:
|
||||||
|
linear-gradient(rgb(118 160 241 / 0.08) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgb(118 160 241 / 0.08) 1px, transparent 1px);
|
||||||
|
opacity: 0.36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-hero-base {
|
.auth-shell-noise {
|
||||||
background:
|
opacity: 0.38;
|
||||||
radial-gradient(circle at 20% 18%, rgb(63 115 255 / 22%) 0, transparent 45%),
|
|
||||||
radial-gradient(circle at 82% 16%, rgb(56 149 255 / 18%) 0, transparent 38%),
|
|
||||||
radial-gradient(circle at 72% 86%, rgb(96 124 255 / 20%) 0, transparent 42%),
|
|
||||||
linear-gradient(150deg, #0a1326 0%, #091022 45%, #0b1529 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-page-desc {
|
.auth-brand-chip {
|
||||||
color: rgb(181 194 226 / 82%);
|
background: rgb(11 19 31 / 0.68);
|
||||||
|
border-color: rgb(138 174 255 / 0.18);
|
||||||
|
box-shadow: 0 24px 48px -34px rgb(0 0 0 / 0.52);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-hero-image {
|
.auth-stage-switcher-label {
|
||||||
filter: drop-shadow(0 26px 52px rgb(8 19 42 / 48%));
|
color: rgb(195 206 230 / 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-orb-left {
|
.auth-stage-pill {
|
||||||
background: rgb(81 126 255 / 24%);
|
background: rgb(11 19 31 / 0.7);
|
||||||
|
border-color: rgb(138 174 255 / 0.18);
|
||||||
|
box-shadow: 0 18px 34px -28px rgb(0 0 0 / 0.46);
|
||||||
|
color: rgb(144 196 255 / 0.92);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-orb-right {
|
.auth-glow-primary {
|
||||||
background: rgb(59 140 248 / 22%);
|
background: radial-gradient(circle, rgb(70 120 255 / 0.26) 0%, rgb(70 120 255 / 0) 70%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-orb-bottom {
|
.auth-glow-secondary {
|
||||||
background: rgb(94 105 239 / 20%);
|
background: radial-gradient(circle, rgb(41 170 201 / 0.18) 0%, rgb(41 170 201 / 0) 72%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-glow-tertiary {
|
||||||
|
background: radial-gradient(circle, rgb(95 128 255 / 0.16) 0%, rgb(95 128 255 / 0) 74%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.auth-page-title {
|
||||||
|
max-width: 14ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.auth-stage {
|
||||||
|
padding-top: 6.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-stage-copy {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-page-title {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-stage-switcher {
|
||||||
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {computed} from 'vue';
|
||||||
|
import {useRoute} from 'vue-router';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'AuthenticationFormView',
|
name: 'AuthenticationFormView',
|
||||||
});
|
});
|
||||||
@@ -6,29 +9,38 @@ defineOptions({
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
dataSide?: 'bottom' | 'left' | 'right' | 'top';
|
dataSide?: 'bottom' | 'left' | 'right' | 'top';
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const isLoginRoute = computed(() => route.path === '/auth/login');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="auth-form-wrap flex-col-center bg-background dark:bg-background-deep relative min-h-full px-6 py-12 lg:px-10"
|
class="auth-form-wrap relative min-h-full"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<div :class="['auth-window-shell', { 'auth-window-shell-plain': isLoginRoute }]">
|
||||||
|
<template v-if="!isLoginRoute">
|
||||||
|
<div class="auth-window-edge auth-window-edge-top"></div>
|
||||||
|
<div class="auth-window-edge auth-window-edge-bottom"></div>
|
||||||
|
</template>
|
||||||
|
<slot></slot>
|
||||||
|
|
||||||
<RouterView v-slot="{ Component, route }">
|
<RouterView v-slot="{ Component, route }">
|
||||||
<Transition appear mode="out-in" name="slide-right">
|
<Transition appear mode="out-in" name="slide-right">
|
||||||
<KeepAlive :include="['Login']">
|
<KeepAlive :include="['Login']">
|
||||||
<component
|
<component
|
||||||
:is="Component"
|
:is="Component"
|
||||||
:key="route.fullPath"
|
:key="route.fullPath"
|
||||||
class="side-content mt-8 w-full sm:mx-auto md:max-w-md"
|
class="side-content w-full"
|
||||||
:data-side="dataSide"
|
:data-side="dataSide"
|
||||||
/>
|
/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</Transition>
|
</Transition>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="text-muted-foreground absolute bottom-4 flex text-center text-xs"
|
class="auth-copyright text-muted-foreground absolute left-1/2 flex -translate-x-1/2 text-center text-xs"
|
||||||
>
|
>
|
||||||
<slot name="copyright"> </slot>
|
<slot name="copyright"> </slot>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,12 +49,91 @@ defineProps<{
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.auth-form-wrap {
|
.auth-form-wrap {
|
||||||
background-image:
|
padding: 0 0 4.5rem;
|
||||||
linear-gradient(180deg, rgb(255 255 255 / 98%) 0%, rgb(247 250 255 / 98%) 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .auth-form-wrap {
|
.auth-window-shell {
|
||||||
background-image:
|
backdrop-filter: blur(22px);
|
||||||
linear-gradient(180deg, rgb(13 20 34 / 96%) 0%, rgb(9 16 29 / 98%) 100%);
|
background:
|
||||||
|
linear-gradient(180deg, rgb(255 255 255 / 0.96) 0%, rgb(248 251 255 / 0.98) 100%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 0.82);
|
||||||
|
border-radius: 2rem;
|
||||||
|
box-shadow:
|
||||||
|
0 40px 80px -48px rgb(13 61 132 / 0.38),
|
||||||
|
0 18px 36px -26px rgb(13 61 132 / 0.18);
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 1.25rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-window-shell-plain {
|
||||||
|
backdrop-filter: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-window-edge {
|
||||||
|
border-radius: 9999px;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-window-edge-top {
|
||||||
|
background: linear-gradient(90deg, rgb(11 111 211 / 0.18), rgb(22 159 200 / 0.08));
|
||||||
|
height: 10rem;
|
||||||
|
left: -4rem;
|
||||||
|
top: -6rem;
|
||||||
|
width: 14rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-window-edge-bottom {
|
||||||
|
background: radial-gradient(circle, rgb(84 132 255 / 0.14) 0%, rgb(84 132 255 / 0) 72%);
|
||||||
|
bottom: -4rem;
|
||||||
|
height: 11rem;
|
||||||
|
right: -4rem;
|
||||||
|
width: 11rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-copyright {
|
||||||
|
bottom: 0.55rem;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .auth-window-shell {
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgb(10 18 30 / 0.92) 0%, rgb(9 17 29 / 0.96) 100%);
|
||||||
|
border-color: rgb(136 168 235 / 0.16);
|
||||||
|
box-shadow:
|
||||||
|
0 42px 84px -50px rgb(0 0 0 / 0.64),
|
||||||
|
0 18px 36px -28px rgb(0 0 0 / 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .auth-window-shell-plain {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .auth-window-edge-top {
|
||||||
|
background: linear-gradient(90deg, rgb(69 120 255 / 0.22), rgb(28 155 197 / 0.08));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .auth-window-edge-bottom {
|
||||||
|
background: radial-gradient(circle, rgb(92 136 255 / 0.16) 0%, rgb(92 136 255 / 0) 72%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.auth-form-wrap {
|
||||||
|
padding-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-window-shell {
|
||||||
|
border-radius: 1.6rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ToolbarType } from './types';
|
import type {ToolbarType} from './types';
|
||||||
|
|
||||||
import { computed } from 'vue';
|
import {computed} from 'vue';
|
||||||
|
|
||||||
import { preferences } from '@easyflow/preferences';
|
import {preferences} from '@easyflow/preferences';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthenticationColorToggle,
|
AuthenticationColorToggle,
|
||||||
@@ -21,7 +21,7 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
toolbarList: () => ['color', 'language', 'layout', 'theme'],
|
toolbarList: () => ['language', 'theme'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const showColor = computed(() => props.toolbarList.includes('color'));
|
const showColor = computed(() => props.toolbarList.includes('color'));
|
||||||
@@ -35,7 +35,7 @@ const showTheme = computed(() => props.toolbarList.includes('theme'));
|
|||||||
:class="{
|
:class="{
|
||||||
'auth-toolbar': toolbarList.length > 1,
|
'auth-toolbar': toolbarList.length > 1,
|
||||||
}"
|
}"
|
||||||
class="flex-center absolute right-3 top-4 z-20"
|
class="flex-center absolute right-4 top-4 z-20 sm:right-6 sm:top-6"
|
||||||
>
|
>
|
||||||
<div class="hidden md:flex">
|
<div class="hidden md:flex">
|
||||||
<AuthenticationColorToggle v-if="showColor" />
|
<AuthenticationColorToggle v-if="showColor" />
|
||||||
@@ -49,16 +49,16 @@ const showTheme = computed(() => props.toolbarList.includes('theme'));
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.auth-toolbar {
|
.auth-toolbar {
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
background: rgb(255 255 255 / 72%);
|
background: rgb(255 255 255 / 0.72);
|
||||||
border: 1px solid rgb(29 108 255 / 10%);
|
border: 1px solid rgb(255 255 255 / 0.78);
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
box-shadow: 0 12px 26px rgb(30 72 152 / 12%);
|
box-shadow: 0 20px 42px -30px rgb(14 55 124 / 0.28);
|
||||||
padding: 0.25rem 0.72rem;
|
padding: 0.25rem 0.58rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.dark) .auth-toolbar {
|
:deep(.dark) .auth-toolbar {
|
||||||
background: rgb(11 19 34 / 66%);
|
background: rgb(11 19 34 / 66%);
|
||||||
border-color: rgb(122 167 255 / 22%);
|
border-color: rgb(122 167 255 / 18%);
|
||||||
box-shadow: 0 12px 28px rgb(0 0 0 / 32%);
|
box-shadow: 0 20px 40px -28px rgb(0 0 0 / 0.46);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
{
|
{
|
||||||
"welcomeBack": "Welcome to EasyFlow User Center",
|
"welcomeBack": "Welcome back to EasyFlow",
|
||||||
"pageTitle": "Integrated AI Workspace",
|
"greetingMorning": "Good morning, welcome to EasyFlow",
|
||||||
"pageDesc": "Multiple chat assistants provide conversational support, while intelligent agents focus on task execution, with both process and results clearly traceable",
|
"greetingNoon": "Good noon, welcome to EasyFlow",
|
||||||
|
"greetingAfternoon": "Good afternoon, welcome to EasyFlow",
|
||||||
|
"greetingEvening": "Good evening, welcome to EasyFlow",
|
||||||
|
"capabilityModel": "Models",
|
||||||
|
"capabilityAgent": "Agents",
|
||||||
|
"capabilityWorkflow": "Workflows",
|
||||||
|
"capabilityKnowledge": "Knowledge",
|
||||||
|
"pageTitle": "Enter the EasyFlow AI Workspace",
|
||||||
|
"pageDesc": "Manage models, agents, workflows, and knowledge in one entry point.",
|
||||||
"loginSuccess": "Login Successful",
|
"loginSuccess": "Login Successful",
|
||||||
"loginSuccessDesc": "Welcome Back",
|
"loginSuccessDesc": "Welcome Back",
|
||||||
"loginSubtitle": "Enter your AI workspace and collaborate with multiple agents and chat assistants.",
|
"loginSubtitle": "Continue your workspace flow.",
|
||||||
"selectAccount": "Quick Select Account",
|
"selectAccount": "Quick Select Account",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
@@ -18,7 +26,7 @@
|
|||||||
"alreadyHaveAccount": "Already have an account?",
|
"alreadyHaveAccount": "Already have an account?",
|
||||||
"accountTip": "Don't have an account?",
|
"accountTip": "Don't have an account?",
|
||||||
"signUp": "Sign Up",
|
"signUp": "Sign Up",
|
||||||
"signUpSubtitle": "Make managing your applications simple and fun",
|
"signUpSubtitle": "Create your workspace account and start building AI collaboration flows with clarity and confidence.",
|
||||||
"confirmPassword": "Confirm Password",
|
"confirmPassword": "Confirm Password",
|
||||||
"confirmPasswordTip": "The passwords do not match",
|
"confirmPasswordTip": "The passwords do not match",
|
||||||
"agree": "I agree to",
|
"agree": "I agree to",
|
||||||
@@ -28,20 +36,20 @@
|
|||||||
"goToLogin": "Login instead",
|
"goToLogin": "Login instead",
|
||||||
"passwordStrength": "Use 8 or more characters with a mix of letters, numbers & symbols",
|
"passwordStrength": "Use 8 or more characters with a mix of letters, numbers & symbols",
|
||||||
"forgetPassword": "Forget Password?",
|
"forgetPassword": "Forget Password?",
|
||||||
"forgetPasswordSubtitle": "Enter your email and we'll send you instructions to reset your password",
|
"forgetPasswordSubtitle": "Enter your email and we will send secure reset instructions to restore access to your workspace.",
|
||||||
"emailTip": "Please enter email",
|
"emailTip": "Please enter email",
|
||||||
"emailValidErrorTip": "The email format you entered is incorrect",
|
"emailValidErrorTip": "The email format you entered is incorrect",
|
||||||
"sendResetLink": "Send Reset Link",
|
"sendResetLink": "Send Reset Link",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"qrcodeSubtitle": "Scan the QR code with your phone to login",
|
"qrcodeSubtitle": "Scan the QR code with your phone for a fast and trusted sign in.",
|
||||||
"qrcodePrompt": "Click 'Confirm' after scanning to complete login",
|
"qrcodePrompt": "Confirm on your phone after scanning to continue this workspace session.",
|
||||||
"qrcodeLogin": "QR Code Login",
|
"qrcodeLogin": "QR Code Login",
|
||||||
"wechatLogin": "Wechat Login",
|
"wechatLogin": "Wechat Login",
|
||||||
"qqLogin": "QQ Login",
|
"qqLogin": "QQ Login",
|
||||||
"githubLogin": "Github Login",
|
"githubLogin": "Github Login",
|
||||||
"googleLogin": "Google Login",
|
"googleLogin": "Google Login",
|
||||||
"dingdingLogin": "Dingding Login",
|
"dingdingLogin": "Dingding Login",
|
||||||
"codeSubtitle": "Enter your phone number to start managing your project",
|
"codeSubtitle": "Enter your phone number and verify with a security code to return to your workspace quickly.",
|
||||||
"code": "Security code",
|
"code": "Security code",
|
||||||
"codeTip": "Security code required {0} characters",
|
"codeTip": "Security code required {0} characters",
|
||||||
"mobile": "Mobile",
|
"mobile": "Mobile",
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
{
|
{
|
||||||
"welcomeBack": "欢迎登录 EasyFlow",
|
"welcomeBack": "欢迎登录 EasyFlow",
|
||||||
"pageTitle": "EasyFlow 智能体开发平台",
|
"greetingMorning": "上午好,欢迎进入 EasyFlow",
|
||||||
"pageDesc": "统一模型管理、Bot 构建与发布、插件系统、RAG 知识库、AI Workflow 智能体编排...",
|
"greetingNoon": "中午好,欢迎进入 EasyFlow",
|
||||||
|
"greetingAfternoon": "下午好,欢迎进入 EasyFlow",
|
||||||
|
"greetingEvening": "晚上好,欢迎进入 EasyFlow",
|
||||||
|
"capabilityModel": "模型",
|
||||||
|
"capabilityAgent": "智能体",
|
||||||
|
"capabilityWorkflow": "工作流",
|
||||||
|
"capabilityKnowledge": "知识库",
|
||||||
|
"pageTitle": "进入 EasyFlow 智能工作台",
|
||||||
|
"pageDesc": "在同一入口管理模型、智能体、工作流与知识库。",
|
||||||
"loginSuccess": "登录成功",
|
"loginSuccess": "登录成功",
|
||||||
"loginSuccessDesc": "欢迎回来",
|
"loginSuccessDesc": "欢迎回来",
|
||||||
"loginSubtitle": "一体化 AI 智能体开发平台",
|
"loginSubtitle": "继续你的工作台任务。",
|
||||||
"selectAccount": "快速选择账号",
|
"selectAccount": "快速选择账号",
|
||||||
"username": "账号",
|
"username": "账号",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
@@ -18,7 +26,7 @@
|
|||||||
"alreadyHaveAccount": "已经有账号了?",
|
"alreadyHaveAccount": "已经有账号了?",
|
||||||
"accountTip": "还没有账号?",
|
"accountTip": "还没有账号?",
|
||||||
"signUp": "注册",
|
"signUp": "注册",
|
||||||
"signUpSubtitle": "让您的应用程序管理变得简单而有趣",
|
"signUpSubtitle": "创建你的工作台账号,开始搭建清晰、可信的 AI 协作流程。",
|
||||||
"confirmPassword": "确认密码",
|
"confirmPassword": "确认密码",
|
||||||
"confirmPasswordTip": "两次输入的密码不一致",
|
"confirmPasswordTip": "两次输入的密码不一致",
|
||||||
"agree": "我同意",
|
"agree": "我同意",
|
||||||
@@ -28,20 +36,20 @@
|
|||||||
"goToLogin": "去登录",
|
"goToLogin": "去登录",
|
||||||
"passwordStrength": "使用 8 个或更多字符,混合字母、数字和符号",
|
"passwordStrength": "使用 8 个或更多字符,混合字母、数字和符号",
|
||||||
"forgetPassword": "忘记密码?",
|
"forgetPassword": "忘记密码?",
|
||||||
"forgetPasswordSubtitle": "输入您的电子邮件,我们将向您发送重置密码的连接",
|
"forgetPasswordSubtitle": "输入账号邮箱,我们会发送一封重置指引,帮助你安全取回访问权限。",
|
||||||
"emailTip": "请输入邮箱",
|
"emailTip": "请输入邮箱",
|
||||||
"emailValidErrorTip": "你输入的邮箱格式不正确",
|
"emailValidErrorTip": "你输入的邮箱格式不正确",
|
||||||
"sendResetLink": "发送重置链接",
|
"sendResetLink": "发送重置链接",
|
||||||
"email": "邮箱",
|
"email": "邮箱",
|
||||||
"qrcodeSubtitle": "请用手机扫描二维码登录",
|
"qrcodeSubtitle": "使用手机扫描二维码,在可信设备上快速完成登录。",
|
||||||
"qrcodePrompt": "扫码后点击 '确认',即可完成登录",
|
"qrcodePrompt": "扫码后在手机上确认,即可继续当前工作台会话。",
|
||||||
"qrcodeLogin": "扫码登录",
|
"qrcodeLogin": "扫码登录",
|
||||||
"wechatLogin": "微信登录",
|
"wechatLogin": "微信登录",
|
||||||
"qqLogin": "QQ登录",
|
"qqLogin": "QQ登录",
|
||||||
"githubLogin": "Github登录",
|
"githubLogin": "Github登录",
|
||||||
"googleLogin": "Google登录",
|
"googleLogin": "Google登录",
|
||||||
"dingdingLogin": "钉钉登录",
|
"dingdingLogin": "钉钉登录",
|
||||||
"codeSubtitle": "请输入您的手机号码以开始管理您的项目",
|
"codeSubtitle": "输入手机号并完成验证码校验,快速返回你的智能工作台。",
|
||||||
"code": "验证码",
|
"code": "验证码",
|
||||||
"codeTip": "请输入{0}位验证码",
|
"codeTip": "请输入{0}位验证码",
|
||||||
"mobile": "手机号码",
|
"mobile": "手机号码",
|
||||||
|
|||||||
Reference in New Issue
Block a user