refactor: refactor AuthLayout to configure the login page more freely (#4294)
parent
a7d322019e
commit
8404c12129
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { AuthPageLayout } from '@vben/layouts';
|
||||||
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const appName = computed(() => preferences.app.name);
|
||||||
|
const logo = computed(() => preferences.logo.source);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AuthPageLayout
|
||||||
|
:app-name="appName"
|
||||||
|
:logo="logo"
|
||||||
|
:page-description="$t('authentication.pageDesc')"
|
||||||
|
:page-title="$t('authentication.pageTitle')"
|
||||||
|
>
|
||||||
|
<!-- 自定义工具栏 -->
|
||||||
|
<!-- <template #toolbar></template> -->
|
||||||
|
</AuthPageLayout>
|
||||||
|
</template>
|
|
@ -1,8 +1,6 @@
|
||||||
const BasicLayout = () => import('./basic.vue');
|
const BasicLayout = () => import('./basic.vue');
|
||||||
|
const AuthPageLayout = () => import('./auth.vue');
|
||||||
|
|
||||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||||
|
|
||||||
const AuthPageLayout = () =>
|
|
||||||
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
|
||||||
|
|
||||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||||
|
|
|
@ -55,12 +55,27 @@ onMounted(() => {
|
||||||
},
|
},
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
},
|
},
|
||||||
|
// xAxis: {
|
||||||
|
// axisTick: {
|
||||||
|
// show: false,
|
||||||
|
// },
|
||||||
|
// boundaryGap: false,
|
||||||
|
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
|
// type: 'category',
|
||||||
|
// },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
type: 'category',
|
type: 'category',
|
||||||
},
|
},
|
||||||
yAxis: [
|
yAxis: [
|
||||||
|
@ -69,7 +84,10 @@ onMounted(() => {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
max: 80_000,
|
max: 80_000,
|
||||||
|
splitArea: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
type: 'value',
|
type: 'value',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { AuthPageLayout } from '@vben/layouts';
|
||||||
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const appName = computed(() => preferences.app.name);
|
||||||
|
const logo = computed(() => preferences.logo.source);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AuthPageLayout
|
||||||
|
:app-name="appName"
|
||||||
|
:logo="logo"
|
||||||
|
:page-description="$t('authentication.pageDesc')"
|
||||||
|
:page-title="$t('authentication.pageTitle')"
|
||||||
|
>
|
||||||
|
<!-- 自定义工具栏 -->
|
||||||
|
<!-- <template #toolbar></template> -->
|
||||||
|
</AuthPageLayout>
|
||||||
|
</template>
|
|
@ -1,8 +1,6 @@
|
||||||
const BasicLayout = () => import('./basic.vue');
|
const BasicLayout = () => import('./basic.vue');
|
||||||
|
const AuthPageLayout = () => import('./auth.vue');
|
||||||
|
|
||||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||||
|
|
||||||
const AuthPageLayout = () =>
|
|
||||||
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
|
||||||
|
|
||||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||||
|
|
|
@ -55,12 +55,27 @@ onMounted(() => {
|
||||||
},
|
},
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
},
|
},
|
||||||
|
// xAxis: {
|
||||||
|
// axisTick: {
|
||||||
|
// show: false,
|
||||||
|
// },
|
||||||
|
// boundaryGap: false,
|
||||||
|
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
|
// type: 'category',
|
||||||
|
// },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
type: 'category',
|
type: 'category',
|
||||||
},
|
},
|
||||||
yAxis: [
|
yAxis: [
|
||||||
|
@ -69,7 +84,10 @@ onMounted(() => {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
max: 80_000,
|
max: 80_000,
|
||||||
|
splitArea: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
type: 'value',
|
type: 'value',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { AuthPageLayout } from '@vben/layouts';
|
||||||
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const appName = computed(() => preferences.app.name);
|
||||||
|
const logo = computed(() => preferences.logo.source);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AuthPageLayout
|
||||||
|
:app-name="appName"
|
||||||
|
:logo="logo"
|
||||||
|
:page-description="$t('authentication.pageDesc')"
|
||||||
|
:page-title="$t('authentication.pageTitle')"
|
||||||
|
>
|
||||||
|
<!-- 自定义工具栏 -->
|
||||||
|
<!-- <template #toolbar></template> -->
|
||||||
|
</AuthPageLayout>
|
||||||
|
</template>
|
|
@ -1,8 +1,6 @@
|
||||||
const BasicLayout = () => import('./basic.vue');
|
const BasicLayout = () => import('./basic.vue');
|
||||||
|
const AuthPageLayout = () => import('./auth.vue');
|
||||||
|
|
||||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||||
|
|
||||||
const AuthPageLayout = () =>
|
|
||||||
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
|
||||||
|
|
||||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||||
|
|
|
@ -55,12 +55,27 @@ onMounted(() => {
|
||||||
},
|
},
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
},
|
},
|
||||||
|
// xAxis: {
|
||||||
|
// axisTick: {
|
||||||
|
// show: false,
|
||||||
|
// },
|
||||||
|
// boundaryGap: false,
|
||||||
|
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
|
// type: 'category',
|
||||||
|
// },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
type: 'category',
|
type: 'category',
|
||||||
},
|
},
|
||||||
yAxis: [
|
yAxis: [
|
||||||
|
@ -69,7 +84,10 @@ onMounted(() => {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
max: 80_000,
|
max: 80_000,
|
||||||
|
splitArea: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
type: 'value',
|
type: 'value',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -240,7 +240,7 @@ const defaultPreferences: Preferences = {
|
||||||
theme: {
|
theme: {
|
||||||
builtinType: 'default',
|
builtinType: 'default',
|
||||||
colorDestructive: 'hsl(348 100% 61%)',
|
colorDestructive: 'hsl(348 100% 61%)',
|
||||||
colorPrimary: 'hsl(231 98% 65%)',
|
colorPrimary: 'hsl(212 100% 45%)',
|
||||||
colorSuccess: 'hsl(144 57% 58%)',
|
colorSuccess: 'hsl(144 57% 58%)',
|
||||||
colorWarning: 'hsl(42 84% 61%)',
|
colorWarning: 'hsl(42 84% 61%)',
|
||||||
mode: 'dark',
|
mode: 'dark',
|
||||||
|
|
|
@ -18,7 +18,7 @@ You just need to configure the `props` parameter of `AuthPageLayout` in `src/rou
|
||||||
pageTitle: "开箱即用的大型中后台管理系统",
|
pageTitle: "开箱即用的大型中后台管理系统",
|
||||||
pageDescription: "工程化、高性能、跨组件库的前端模版",
|
pageDescription: "工程化、高性能、跨组件库的前端模版",
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
toolbarList: () => ['color', 'language', 'layout', 'theme'],
|
toolbarList: ['color', 'language', 'layout', 'theme'],
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,7 +53,7 @@ You can check the list below to understand all the available variables.
|
||||||
|
|
||||||
/* Theme Colors */
|
/* Theme Colors */
|
||||||
|
|
||||||
--primary: 231 98% 65%;
|
--primary: 212 100% 45%;
|
||||||
--primary-foreground: 0 0% 98%;
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||||
|
@ -264,7 +264,7 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||||
// Error color
|
// Error color
|
||||||
colorDestructive: 'hsl(348 100% 61%)',
|
colorDestructive: 'hsl(348 100% 61%)',
|
||||||
// Primary color
|
// Primary color
|
||||||
colorPrimary: 'hsl(231 98% 65%)',
|
colorPrimary: 'hsl(212 100% 45%)',
|
||||||
// Success color
|
// Success color
|
||||||
colorSuccess: 'hsl(144 57% 58%)',
|
colorSuccess: 'hsl(144 57% 58%)',
|
||||||
// Warning color
|
// Warning color
|
||||||
|
@ -351,7 +351,7 @@ type BuiltinThemeType =
|
||||||
|
|
||||||
/* Theme Colors */
|
/* Theme Colors */
|
||||||
|
|
||||||
--primary: 231 98% 65%;
|
--primary: 212 100% 45%;
|
||||||
--primary-foreground: 0 0% 98%;
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||||
|
|
|
@ -262,7 +262,7 @@ const defaultPreferences: Preferences = {
|
||||||
theme: {
|
theme: {
|
||||||
builtinType: 'default',
|
builtinType: 'default',
|
||||||
colorDestructive: 'hsl(348 100% 61%)',
|
colorDestructive: 'hsl(348 100% 61%)',
|
||||||
colorPrimary: 'hsl(231 98% 65%)',
|
colorPrimary: 'hsl(212 100% 45%)',
|
||||||
colorSuccess: 'hsl(144 57% 58%)',
|
colorSuccess: 'hsl(144 57% 58%)',
|
||||||
colorWarning: 'hsl(42 84% 61%)',
|
colorWarning: 'hsl(42 84% 61%)',
|
||||||
mode: 'dark',
|
mode: 'dark',
|
||||||
|
|
|
@ -8,28 +8,21 @@
|
||||||
|
|
||||||
![login](/guide/login.png)
|
![login](/guide/login.png)
|
||||||
|
|
||||||
只需要在应用下的 `src/router/routes/core.ts` 内,配置`AuthPageLayout`的 `props`参数即可:
|
只需要在应用下的 `src/layouts/auth.vue` 内,配置`AuthPageLayout`的 `props`参数即可:
|
||||||
|
|
||||||
```ts {4-8}
|
```vue {2-7}
|
||||||
{
|
<AuthPageLayout
|
||||||
component: AuthPageLayout,
|
:copyright="true"
|
||||||
props: {
|
:toolbar="true"
|
||||||
sloganImage: "xxx/xxx.png",
|
:toolbarList="['color', 'language', 'layout', 'theme']"
|
||||||
pageTitle: "开箱即用的大型中后台管理系统",
|
:app-name="appName"
|
||||||
pageDescription: "工程化、高性能、跨组件库的前端模版",
|
:logo="logo"
|
||||||
toolbar: true,
|
:page-description="$t('authentication.pageDesc')"
|
||||||
toolbarList: () => ['color', 'language', 'layout', 'theme'],
|
:page-title="$t('authentication.pageTitle')"
|
||||||
}
|
>
|
||||||
// ...
|
</AuthPageLayout>
|
||||||
},
|
|
||||||
```
|
```
|
||||||
|
|
||||||
::: tip
|
|
||||||
|
|
||||||
如果这些配置不能满足你的需求,你可以自行实现登录页面。直接实现自己的 `AuthPageLayout`即可。
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## 登录表单调整
|
## 登录表单调整
|
||||||
|
|
||||||
如果你想调整登录表单的相关内容,你可以在应用下的 `src/views/_core/authentication/login.vue` 内,配置`AuthenticationLogin` 组件参数即可:
|
如果你想调整登录表单的相关内容,你可以在应用下的 `src/views/_core/authentication/login.vue` 内,配置`AuthenticationLogin` 组件参数即可:
|
||||||
|
|
|
@ -53,7 +53,7 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要
|
||||||
|
|
||||||
/* 主题颜色 */
|
/* 主题颜色 */
|
||||||
|
|
||||||
--primary: 231 98% 65%;
|
--primary: 212 100% 45%;
|
||||||
--primary-foreground: 0 0% 98%;
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||||
|
@ -264,7 +264,7 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||||
// 错误色
|
// 错误色
|
||||||
colorDestructive: 'hsl(348 100% 61%)',
|
colorDestructive: 'hsl(348 100% 61%)',
|
||||||
// 主题色
|
// 主题色
|
||||||
colorPrimary: 'hsl(231 98% 65%)',
|
colorPrimary: 'hsl(212 100% 45%)',
|
||||||
// 成功色
|
// 成功色
|
||||||
colorSuccess: 'hsl(144 57% 58%)',
|
colorSuccess: 'hsl(144 57% 58%)',
|
||||||
// 警告色
|
// 警告色
|
||||||
|
@ -351,7 +351,7 @@ type BuiltinThemeType =
|
||||||
|
|
||||||
/* 主题颜色 */
|
/* 主题颜色 */
|
||||||
|
|
||||||
--primary: 231 98% 65%;
|
--primary: 212 100% 45%;
|
||||||
--primary-foreground: 0 0% 98%;
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
/* 主题颜色 */
|
/* 主题颜色 */
|
||||||
|
|
||||||
--primary: 231 98% 65%;
|
--primary: 212 100% 45%;
|
||||||
--primary-foreground: 0 0% 98%;
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||||
|
@ -78,6 +78,7 @@
|
||||||
|
|
||||||
/* 遮罩颜色 */
|
/* 遮罩颜色 */
|
||||||
--overlay: 0 0% 0% / 45%;
|
--overlay: 0 0% 0% / 45%;
|
||||||
|
--overlay-light: 0 0% 95% / 45%;
|
||||||
|
|
||||||
/* 基本文字大小 */
|
/* 基本文字大小 */
|
||||||
--font-size-base: 16px;
|
--font-size-base: 16px;
|
||||||
|
|
|
@ -87,7 +87,7 @@ const defaultPreferences: Preferences = {
|
||||||
theme: {
|
theme: {
|
||||||
builtinType: 'default',
|
builtinType: 'default',
|
||||||
colorDestructive: 'hsl(348 100% 61%)',
|
colorDestructive: 'hsl(348 100% 61%)',
|
||||||
colorPrimary: 'hsl(231 98% 65%)',
|
colorPrimary: 'hsl(212 100% 45%)',
|
||||||
colorSuccess: 'hsl(144 57% 58%)',
|
colorSuccess: 'hsl(144 57% 58%)',
|
||||||
colorWarning: 'hsl(42 84% 61%)',
|
colorWarning: 'hsl(42 84% 61%)',
|
||||||
mode: 'dark',
|
mode: 'dark',
|
||||||
|
|
|
@ -9,7 +9,7 @@ interface BuiltinThemePreset {
|
||||||
|
|
||||||
const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
|
const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
|
||||||
{
|
{
|
||||||
color: 'hsl(231 98% 65%)',
|
color: 'hsl(212 100% 45%)',
|
||||||
type: 'default',
|
type: 'default',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,7 @@ const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
|
||||||
type: 'yellow',
|
type: 'yellow',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: 'hsl(212 100% 45%)',
|
color: 'hsl(231 98% 65%)',
|
||||||
type: 'sky-blue',
|
type: 'sky-blue',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -159,7 +159,7 @@ function pointerDownOutside(e: Event) {
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
:show-close="closable"
|
:show-close="closable"
|
||||||
close-class="top-4"
|
close-class="top-3"
|
||||||
@escape-key-down="escapeKeyDown"
|
@escape-key-down="escapeKeyDown"
|
||||||
@interact-outside="interactOutside"
|
@interact-outside="interactOutside"
|
||||||
@pointer-down-outside="pointerDownOutside"
|
@pointer-down-outside="pointerDownOutside"
|
||||||
|
@ -219,7 +219,7 @@ function pointerDownOutside(e: Event) {
|
||||||
|
|
||||||
<VbenIconButton
|
<VbenIconButton
|
||||||
v-if="fullscreenButton"
|
v-if="fullscreenButton"
|
||||||
class="hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-10 top-4 hidden size-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none sm:block"
|
class="hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-10 top-3 hidden size-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none sm:block"
|
||||||
@click="handleFullscreen"
|
@click="handleFullscreen"
|
||||||
>
|
>
|
||||||
<Shrink v-if="fullscreen" class="size-3.5" />
|
<Shrink v-if="fullscreen" class="size-3.5" />
|
||||||
|
|
|
@ -25,7 +25,7 @@ const show = ref(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="relative">
|
<div class="relative">
|
||||||
<VbenInput
|
<VbenInput
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
v-bind="{ ...forward, ...$attrs }"
|
v-bind="{ ...forward, ...$attrs }"
|
||||||
|
@ -48,5 +48,5 @@ const show = ref(false);
|
||||||
<Eye v-if="show" class="size-4" />
|
<Eye v-if="show" class="size-4" />
|
||||||
<EyeOff v-else class="size-4" />
|
<EyeOff v-else class="size-4" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -69,7 +69,7 @@ function onTransitionEnd() {
|
||||||
<div
|
<div
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'bg-overlay z-100 pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center transition-all duration-500',
|
'z-100 pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center bg-[hsl(var(--overlay-light))] transition-all duration-500',
|
||||||
{
|
{
|
||||||
'invisible opacity-0': !showSpinner,
|
'invisible opacity-0': !showSpinner,
|
||||||
},
|
},
|
||||||
|
|
|
@ -63,7 +63,7 @@ function onTransitionEnd() {
|
||||||
<div
|
<div
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'flex-center bg-overlay z-100 absolute left-0 top-0 size-full backdrop-blur-sm transition-all duration-500',
|
'flex-center z-100 absolute left-0 top-0 size-full bg-[hsl(var(--overlay-light))] backdrop-blur-sm transition-all duration-500',
|
||||||
{
|
{
|
||||||
'invisible opacity-0': !showSpinner,
|
'invisible opacity-0': !showSpinner,
|
||||||
},
|
},
|
||||||
|
|
|
@ -92,14 +92,16 @@ function handleGo(path: string) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div @keydown.enter.prevent="handleSubmit">
|
<div @keydown.enter.prevent="handleSubmit">
|
||||||
<Title>
|
<slot name="title">
|
||||||
{{ title || `${$t('authentication.welcomeBack')} 👋🏻` }}
|
<Title>
|
||||||
<template #desc>
|
{{ title || `${$t('authentication.welcomeBack')} 👋🏻` }}
|
||||||
<span class="text-muted-foreground">
|
<template #desc>
|
||||||
{{ subTitle || $t('authentication.loginSubtitle') }}
|
<span class="text-muted-foreground">
|
||||||
</span>
|
{{ subTitle || $t('authentication.loginSubtitle') }}
|
||||||
</template>
|
</span>
|
||||||
</Title>
|
</template>
|
||||||
|
</Title>
|
||||||
|
</slot>
|
||||||
|
|
||||||
<VbenInput
|
<VbenInput
|
||||||
v-model="formState.username"
|
v-model="formState.username"
|
||||||
|
@ -123,7 +125,10 @@ function handleGo(path: string) {
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mb-6 mt-4 flex justify-between">
|
<div
|
||||||
|
v-if="showRememberMe || showForgetPassword"
|
||||||
|
class="mb-6 mt-4 flex justify-between"
|
||||||
|
>
|
||||||
<div v-if="showRememberMe" class="flex-center">
|
<div v-if="showRememberMe" class="flex-center">
|
||||||
<VbenCheckbox v-model:checked="formState.rememberMe" name="rememberMe">
|
<VbenCheckbox v-model:checked="formState.rememberMe" name="rememberMe">
|
||||||
{{ $t('authentication.rememberMe') }}
|
{{ $t('authentication.rememberMe') }}
|
||||||
|
@ -142,7 +147,10 @@ function handleGo(path: string) {
|
||||||
{{ $t('common.login') }}
|
{{ $t('common.login') }}
|
||||||
</VbenButton>
|
</VbenButton>
|
||||||
|
|
||||||
<div class="mb-2 mt-4 flex items-center justify-between">
|
<div
|
||||||
|
v-if="showCodeLogin || showQrcodeLogin"
|
||||||
|
class="mb-2 mt-4 flex items-center justify-between"
|
||||||
|
>
|
||||||
<VbenButton
|
<VbenButton
|
||||||
v-if="showCodeLogin"
|
v-if="showCodeLogin"
|
||||||
class="w-1/2"
|
class="w-1/2"
|
||||||
|
@ -162,16 +170,20 @@ function handleGo(path: string) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第三方登录 -->
|
<!-- 第三方登录 -->
|
||||||
<ThirdPartyLogin v-if="showThirdPartyLogin" />
|
<slot name="third-party-login">
|
||||||
|
<ThirdPartyLogin v-if="showThirdPartyLogin" />
|
||||||
|
</slot>
|
||||||
|
|
||||||
<div v-if="showRegister" class="text-center text-sm">
|
<slot name="to-register">
|
||||||
{{ $t('authentication.accountTip') }}
|
<div v-if="showRegister" class="mt-3 text-center text-sm">
|
||||||
<span
|
{{ $t('authentication.accountTip') }}
|
||||||
class="text-primary hover:text-primary-hover active:text-primary-active cursor-pointer text-sm font-normal"
|
<span
|
||||||
@click="handleGo(registerPath)"
|
class="text-primary hover:text-primary-hover active:text-primary-active cursor-pointer text-sm font-normal"
|
||||||
>
|
@click="handleGo(registerPath)"
|
||||||
{{ $t('authentication.createAccount') }}
|
>
|
||||||
</span>
|
{{ $t('authentication.createAccount') }}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,24 +1,28 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import type { ToolbarType } from './types';
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
import { preferences, usePreferences } from '@vben/preferences';
|
import { preferences, usePreferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
import { Copyright } from '../basic/copyright';
|
||||||
import AuthenticationFormView from './form.vue';
|
import AuthenticationFormView from './form.vue';
|
||||||
import SloganIcon from './icons/slogan.vue';
|
import SloganIcon from './icons/slogan.vue';
|
||||||
import Toolbar from './toolbar.vue';
|
import Toolbar from './toolbar.vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
appName?: string;
|
||||||
|
logo?: string;
|
||||||
pageTitle?: string;
|
pageTitle?: string;
|
||||||
pageDescription?: string;
|
pageDescription?: string;
|
||||||
sloganImage?: string;
|
sloganImage?: string;
|
||||||
toolbar?: boolean;
|
toolbar?: boolean;
|
||||||
toolbarList?: ('color' | 'language' | 'layout' | 'theme')[];
|
copyright?: boolean;
|
||||||
|
toolbarList?: ToolbarType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({ name: 'Authentication' });
|
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
|
appName: '',
|
||||||
|
copyright: true,
|
||||||
|
logo: '',
|
||||||
pageDescription: '',
|
pageDescription: '',
|
||||||
pageTitle: '',
|
pageTitle: '',
|
||||||
sloganImage: '',
|
sloganImage: '',
|
||||||
|
@ -26,31 +30,42 @@ withDefaults(defineProps<Props>(), {
|
||||||
toolbarList: () => ['color', 'language', 'layout', 'theme'],
|
toolbarList: () => ['color', 'language', 'layout', 'theme'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { authPanelCenter, authPanelLeft, authPanelRight } = usePreferences();
|
const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
|
||||||
const appName = computed(() => preferences.app.name);
|
usePreferences();
|
||||||
const logoSource = computed(() => preferences.logo.source);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex min-h-full flex-1 select-none overflow-x-hidden">
|
<div
|
||||||
|
:class="[isDark]"
|
||||||
|
class="flex min-h-full flex-1 select-none overflow-x-hidden"
|
||||||
|
>
|
||||||
|
<template v-if="toolbar">
|
||||||
|
<slot name="toolbar">
|
||||||
|
<Toolbar :toolbar-list="toolbarList" />
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
<!-- 左侧认证面板 -->
|
<!-- 左侧认证面板 -->
|
||||||
<AuthenticationFormView
|
<AuthenticationFormView
|
||||||
v-if="authPanelLeft"
|
v-if="authPanelLeft"
|
||||||
class="min-h-full w-2/5 flex-1"
|
class="min-h-full w-2/5 flex-1"
|
||||||
transition-name="slide-left"
|
transition-name="slide-left"
|
||||||
>
|
>
|
||||||
<template v-if="toolbar" #toolbar>
|
<template v-if="copyright" #copyright>
|
||||||
<Toolbar :toolbar-list="toolbarList" />
|
<slot name="copyright">
|
||||||
|
<Copyright
|
||||||
|
v-if="preferences.copyright.enable"
|
||||||
|
v-bind="preferences.copyright"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
</AuthenticationFormView>
|
</AuthenticationFormView>
|
||||||
|
|
||||||
<!-- 头部 Logo 和应用名称 -->
|
<!-- 头部 Logo 和应用名称 -->
|
||||||
<div class="absolute left-0 top-0 z-10 flex flex-1">
|
<div class="absolute left-0 top-0 z-10 flex flex-1">
|
||||||
<div
|
<div
|
||||||
:class="authPanelRight ? 'lg:text-white' : 'lg:text-foreground'"
|
class="text-foreground lg:text-foreground ml-4 mt-4 flex flex-1 items-center sm:left-6 sm:top-6"
|
||||||
class="text-foreground ml-4 mt-4 flex flex-1 items-center sm:left-6 sm:top-6"
|
|
||||||
>
|
>
|
||||||
<img :alt="appName" :src="logoSource" class="mr-2" width="42" />
|
<img :alt="appName" :src="logo" class="mr-2" width="42" />
|
||||||
<p class="text-xl font-medium">
|
<p class="text-xl font-medium">
|
||||||
{{ appName }}
|
{{ appName }}
|
||||||
</p>
|
</p>
|
||||||
|
@ -59,7 +74,9 @@ const logoSource = computed(() => preferences.logo.source);
|
||||||
|
|
||||||
<!-- 系统介绍 -->
|
<!-- 系统介绍 -->
|
||||||
<div v-if="!authPanelCenter" class="relative hidden w-0 flex-1 lg:block">
|
<div v-if="!authPanelCenter" class="relative hidden w-0 flex-1 lg:block">
|
||||||
<div class="absolute inset-0 h-full w-full bg-[#070709]">
|
<div
|
||||||
|
class="bg-background-deep absolute inset-0 h-full w-full dark:bg-[#070709]"
|
||||||
|
>
|
||||||
<div class="login-background absolute left-0 top-0 size-full"></div>
|
<div class="login-background absolute left-0 top-0 size-full"></div>
|
||||||
<div class="flex-col-center -enter-x mr-20 h-full">
|
<div class="flex-col-center -enter-x mr-20 h-full">
|
||||||
<template v-if="sloganImage">
|
<template v-if="sloganImage">
|
||||||
|
@ -70,11 +87,11 @@ const logoSource = computed(() => preferences.logo.source);
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<SloganIcon v-else :alt="appName" class="animate-float h-64 w-2/5" />
|
<SloganIcon v-else :alt="appName" class="animate-float h-64 w-2/5" />
|
||||||
<div class="text-1xl mt-6 font-sans text-white lg:text-2xl">
|
<div class="text-1xl text-foreground mt-6 font-sans lg:text-2xl">
|
||||||
{{ pageTitle || $t('authentication.pageTitle') }}
|
{{ pageTitle }}
|
||||||
</div>
|
</div>
|
||||||
<div class="dark:text-muted-foreground mt-2 text-white/60">
|
<div class="dark:text-muted-foreground mt-2">
|
||||||
{{ pageDescription || $t('authentication.pageDesc') }}
|
{{ pageDescription }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,10 +101,15 @@ const logoSource = computed(() => preferences.logo.source);
|
||||||
<div v-if="authPanelCenter" class="flex-center relative w-full">
|
<div v-if="authPanelCenter" class="flex-center relative w-full">
|
||||||
<div class="login-background absolute left-0 top-0 size-full"></div>
|
<div class="login-background absolute left-0 top-0 size-full"></div>
|
||||||
<AuthenticationFormView
|
<AuthenticationFormView
|
||||||
class="md:bg-background shadow-primary/10 w-full rounded-3xl pb-20 shadow-2xl md:w-2/3 lg:w-1/2 xl:w-[36%]"
|
class="md:bg-background shadow-primary/5 shadow-float w-full rounded-3xl pb-20 md:w-2/3 lg:w-1/2 xl:w-[36%]"
|
||||||
>
|
>
|
||||||
<template v-if="toolbar" #toolbar>
|
<template v-if="copyright" #copyright>
|
||||||
<Toolbar :toolbar-list="toolbarList" />
|
<slot name="copyright">
|
||||||
|
<Copyright
|
||||||
|
v-if="preferences.copyright.enable"
|
||||||
|
v-bind="preferences.copyright"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
</AuthenticationFormView>
|
</AuthenticationFormView>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,10 +117,15 @@ const logoSource = computed(() => preferences.logo.source);
|
||||||
<!-- 右侧认证面板 -->
|
<!-- 右侧认证面板 -->
|
||||||
<AuthenticationFormView
|
<AuthenticationFormView
|
||||||
v-if="authPanelRight"
|
v-if="authPanelRight"
|
||||||
class="min-h-full w-2/5 flex-1"
|
class="min-h-full w-[34%] flex-1"
|
||||||
>
|
>
|
||||||
<template v-if="toolbar" #toolbar>
|
<template v-if="copyright" #copyright>
|
||||||
<Toolbar :toolbar-list="toolbarList" />
|
<slot name="copyright">
|
||||||
|
<Copyright
|
||||||
|
v-if="preferences.copyright.enable"
|
||||||
|
v-bind="preferences.copyright"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
</AuthenticationFormView>
|
</AuthenticationFormView>
|
||||||
</div>
|
</div>
|
||||||
|
@ -109,9 +136,21 @@ const logoSource = computed(() => preferences.logo.source);
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
154deg,
|
154deg,
|
||||||
#07070915 30%,
|
#07070915 30%,
|
||||||
hsl(var(--primary) / 15%) 48%,
|
hsl(var(--primary) / 30%) 48%,
|
||||||
#07070915 64%
|
#07070915 64%
|
||||||
);
|
);
|
||||||
filter: blur(100px);
|
filter: blur(100px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
.login-background {
|
||||||
|
background: linear-gradient(
|
||||||
|
154deg,
|
||||||
|
#07070915 30%,
|
||||||
|
hsl(var(--primary) / 20%) 48%,
|
||||||
|
#07070915 64%
|
||||||
|
);
|
||||||
|
filter: blur(100px);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { preferences } from '@vben/preferences';
|
|
||||||
|
|
||||||
import { Copyright } from '../basic/copyright';
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'AuthenticationFormView',
|
name: 'AuthenticationFormView',
|
||||||
});
|
});
|
||||||
|
@ -10,11 +6,9 @@ defineOptions({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex-col-center bg-background-deep relative px-6 py-10 lg:flex-initial lg:px-8"
|
class="flex-col-center dark:bg-background-deep bg-background relative px-6 py-10 lg:flex-initial lg:px-8"
|
||||||
>
|
>
|
||||||
<!-- Toolbar Slot -->
|
<slot></slot>
|
||||||
<slot name="toolbar"> </slot>
|
|
||||||
|
|
||||||
<!-- Router View with Transition and KeepAlive -->
|
<!-- Router View with Transition and KeepAlive -->
|
||||||
<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">
|
||||||
|
@ -29,13 +23,11 @@ defineOptions({
|
||||||
</RouterView>
|
</RouterView>
|
||||||
|
|
||||||
<!-- Footer Copyright -->
|
<!-- Footer Copyright -->
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="text-muted-foreground absolute bottom-3 flex text-center text-xs"
|
class="text-muted-foreground absolute bottom-3 flex text-center text-xs"
|
||||||
>
|
>
|
||||||
<Copyright
|
<slot name="copyright"> </slot>
|
||||||
v-if="preferences.copyright.enable"
|
|
||||||
v-bind="preferences.copyright"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { ToolbarType } from './types';
|
||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -9,7 +11,7 @@ import {
|
||||||
} from '../widgets';
|
} from '../widgets';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
toolbarList?: ('color' | 'language' | 'layout' | 'theme')[];
|
toolbarList?: ToolbarType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
@ -29,8 +31,7 @@ const showTheme = computed(() => props.toolbarList.includes('theme'));
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="{
|
:class="{
|
||||||
'bg-background dark:bg-accent rounded-3xl px-3 py-1':
|
'bg-accent z-10 rounded-3xl px-3 py-1': toolbarList.length > 1,
|
||||||
toolbarList.length > 1,
|
|
||||||
}"
|
}"
|
||||||
class="flex-center absolute right-2 top-4"
|
class="flex-center absolute right-2 top-4"
|
||||||
>
|
>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export type ToolbarType = 'color' | 'language' | 'layout' | 'theme';
|
|
@ -95,6 +95,10 @@ function handleNotice(versionTag: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
|
if (props.checkUpdatesInterval <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 每 checkUpdatesInterval(默认值为1) 分钟检查一次
|
// 每 checkUpdatesInterval(默认值为1) 分钟检查一次
|
||||||
timer.value = setInterval(
|
timer.value = setInterval(
|
||||||
checkForUpdates,
|
checkForUpdates,
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { AuthPageLayout } from '@vben/layouts';
|
||||||
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const appName = computed(() => preferences.app.name);
|
||||||
|
const logo = computed(() => preferences.logo.source);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AuthPageLayout
|
||||||
|
:app-name="appName"
|
||||||
|
:logo="logo"
|
||||||
|
:page-description="$t('authentication.pageDesc')"
|
||||||
|
:page-title="$t('authentication.pageTitle')"
|
||||||
|
>
|
||||||
|
<!-- 自定义工具栏 -->
|
||||||
|
<!-- <template #toolbar></template> -->
|
||||||
|
</AuthPageLayout>
|
||||||
|
</template>
|
|
@ -1,8 +1,6 @@
|
||||||
const BasicLayout = () => import('./basic.vue');
|
const BasicLayout = () => import('./basic.vue');
|
||||||
|
const AuthPageLayout = () => import('./auth.vue');
|
||||||
|
|
||||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||||
|
|
||||||
const AuthPageLayout = () =>
|
|
||||||
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
|
||||||
|
|
||||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||||
|
|
|
@ -55,12 +55,27 @@ onMounted(() => {
|
||||||
},
|
},
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
},
|
},
|
||||||
|
// xAxis: {
|
||||||
|
// axisTick: {
|
||||||
|
// show: false,
|
||||||
|
// },
|
||||||
|
// boundaryGap: false,
|
||||||
|
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
|
// type: 'category',
|
||||||
|
// },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
type: 'category',
|
type: 'category',
|
||||||
},
|
},
|
||||||
yAxis: [
|
yAxis: [
|
||||||
|
@ -69,7 +84,10 @@ onMounted(() => {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
max: 80_000,
|
max: 80_000,
|
||||||
|
splitArea: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
type: 'value',
|
type: 'value',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue