Browse Source

perf: supplement login interface documents and add configuration parameters (#4175)

Vben 7 months ago
parent
commit
5f41c51770

+ 2 - 1
docs/.vitepress/config.mts

@@ -13,7 +13,7 @@ import { version } from '../../package.json';
 
 export default withPwa(
   defineConfigWithTheme({
-    description: 'Vben Admin& 企业级管理系统框架',
+    description: 'Vben Admin & 企业级管理系统框架',
     head: head(),
     lang: 'zh',
     pwa: pwa(),
@@ -284,6 +284,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] {
     {
       text: '深入',
       items: [
+        { link: 'in-depth/login', text: '登录' },
         // { link: 'in-depth/layout', text: '布局' },
         { link: 'in-depth/theme', text: '主题' },
         { link: 'in-depth/access', text: '权限' },

+ 131 - 0
docs/src/guide/in-depth/login.md

@@ -0,0 +1,131 @@
+# 登录
+
+本文介绍如何去改造自己的应用程序登录页。
+
+## 登录页面调整
+
+如果你想调整登录页面的标题、描述和图标以及工具栏,你可以通过配置 `AuthPageLayout` 组件的 `props` 参数来实现。
+
+![login](/guide/login.png)
+
+只需要在应用下的 `src/router/routes/core.ts` 内,配置`AuthPageLayout`的 `props`参数即可:
+
+```ts {4-8}
+ {
+    component: AuthPageLayout,
+    props: {
+      sloganImage: "xxx/xxx.png",
+      pageTitle: "开箱即用的大型中后台管理系统",
+      pageDescription: "工程化、高性能、跨组件库的前端模版",
+      toolbar: true,
+      toolbarList: () => ['color', 'language', 'layout', 'theme'],
+    }
+    // ...
+  },
+```
+
+::: tip
+
+如果这些配置不能满足你的需求,你可以自行实现登录页面。直接实现自己的 `AuthPageLayout`即可。
+
+:::
+
+## 登录表单调整
+
+如果你想调整登录表单的相关内容,你可以在应用下的 `src/views/_core/authentication/login.vue` 内,配置`AuthenticationLogin` 组件参数即可:
+
+```vue
+<AuthenticationLogin
+  :loading="authStore.loginLoading"
+  password-placeholder="123456"
+  username-placeholder="vben"
+  @submit="authStore.authLogin"
+/>
+```
+
+::: details AuthenticationLogin 组件参数
+
+```ts
+{
+  /**
+   * @zh_CN 验证码登录路径
+   */
+  codeLoginPath?: string;
+  /**
+   * @zh_CN 忘记密码路径
+   */
+  forgetPasswordPath?: string;
+
+  /**
+   * @zh_CN 是否处于加载处理状态
+   */
+  loading?: boolean;
+
+  /**
+   * @zh_CN 密码占位符
+   */
+  passwordPlaceholder?: string;
+
+  /**
+   * @zh_CN 二维码登录路径
+   */
+  qrCodeLoginPath?: string;
+
+  /**
+   * @zh_CN 注册路径
+   */
+  registerPath?: string;
+
+  /**
+   * @zh_CN 是否显示验证码登录
+   */
+  showCodeLogin?: boolean;
+  /**
+   * @zh_CN 是否显示忘记密码
+   */
+  showForgetPassword?: boolean;
+
+  /**
+   * @zh_CN 是否显示二维码登录
+   */
+  showQrcodeLogin?: boolean;
+
+  /**
+   * @zh_CN 是否显示注册按钮
+   */
+  showRegister?: boolean;
+
+  /**
+   * @zh_CN 是否显示记住账号
+   */
+  showRememberMe?: boolean;
+
+  /**
+   * @zh_CN 是否显示第三方登录
+   */
+  showThirdPartyLogin?: boolean;
+
+  /**
+   * @zh_CN 登录框子标题
+   */
+  subTitle?: string;
+
+  /**
+   * @zh_CN 登录框标题
+   */
+  title?: string;
+
+  /**
+   * @zh_CN 用户名占位符
+   */
+  usernamePlaceholder?: string;
+}
+```
+
+:::
+
+::: tip
+
+如果这些配置不能满足你的需求,你可以自行实现登录表单及相关登录逻辑。
+
+:::

BIN
docs/src/public/guide/login.png


+ 1 - 0
packages/@core/base/shared/build.config.ts

@@ -5,6 +5,7 @@ export default defineBuildConfig({
   declaration: true,
   entries: [
     'src/index',
+    'src/store',
     'src/constants/index',
     'src/utils/index',
     'src/color/index',

+ 6 - 0
packages/@core/base/shared/package.json

@@ -44,6 +44,11 @@
       "types": "./src/cache/index.ts",
       "development": "./src/cache/index.ts",
       "default": "./dist/cache/index.mjs"
+    },
+    "./store": {
+      "types": "./src/store.ts",
+      "development": "./src/store.ts",
+      "default": "./dist/store.mjs"
     }
   },
   "publishConfig": {
@@ -56,6 +61,7 @@
   },
   "dependencies": {
     "@ctrl/tinycolor": "^4.1.0",
+    "@tanstack/vue-store": "^0.5.5",
     "@vue/shared": "^3.4.37",
     "clsx": "^2.1.1",
     "defu": "^6.1.4",

+ 1 - 0
packages/@core/base/shared/src/index.ts

@@ -1,4 +1,5 @@
 export * from './cache';
 export * from './color';
 export * from './constants';
+export * from './store';
 export * from './utils';

+ 1 - 0
packages/@core/base/shared/src/store.ts

@@ -0,0 +1 @@
+export * from '@tanstack/vue-store';

+ 0 - 1
packages/@core/preferences/src/preferences.ts

@@ -116,7 +116,6 @@ class PreferenceManager {
         this.updatePreferences({
           theme: { mode: isDark ? 'dark' : 'light' },
         });
-        // updateCSSVariables(this.state);
       });
   }
 

+ 42 - 6
packages/effects/layouts/src/authentication/authentication.vue

@@ -6,9 +6,26 @@ import { preferences, usePreferences } from '@vben/preferences';
 
 import AuthenticationFormView from './form.vue';
 import SloganIcon from './icons/slogan.vue';
+import Toolbar from './toolbar.vue';
+
+interface Props {
+  pageTitle?: string;
+  pageDescription?: string;
+  sloganImage?: string;
+  toolbar?: boolean;
+  toolbarList?: ('color' | 'language' | 'layout' | 'theme')[];
+}
 
 defineOptions({ name: 'Authentication' });
 
+withDefaults(defineProps<Props>(), {
+  pageDescription: '',
+  pageTitle: '',
+  sloganImage: '',
+  toolbar: true,
+  toolbarList: () => ['color', 'language', 'layout', 'theme'],
+});
+
 const { authPanelCenter, authPanelLeft, authPanelRight } = usePreferences();
 const appName = computed(() => preferences.app.name);
 const logoSource = computed(() => preferences.logo.source);
@@ -21,7 +38,11 @@ const logoSource = computed(() => preferences.logo.source);
       v-if="authPanelLeft"
       class="min-h-full w-2/5"
       transition-name="slide-left"
-    />
+    >
+      <template v-if="toolbar" #toolbar>
+        <Toolbar :toolbar-list="toolbarList" />
+      </template>
+    </AuthenticationFormView>
 
     <!-- 头部 Logo 和应用名称 -->
     <div class="absolute left-0 top-0 z-10 flex flex-1">
@@ -41,12 +62,19 @@ const logoSource = computed(() => preferences.logo.source);
       <div class="absolute inset-0 h-full w-full bg-[#070709]">
         <div class="login-background absolute left-0 top-0 size-full"></div>
         <div class="flex-col-center -enter-x mr-20 h-full">
-          <SloganIcon :alt="appName" class="animate-float h-64 w-2/5" />
+          <template v-if="sloganImage">
+            <img
+              :alt="appName"
+              :src="sloganImage"
+              class="animate-float h-64 w-2/5"
+            />
+          </template>
+          <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">
-            {{ $t('authentication.pageTitle') }}
+            {{ pageTitle || $t('authentication.pageTitle') }}
           </div>
           <div class="dark:text-muted-foreground mt-2 text-white/60">
-            {{ $t('authentication.pageDesc') }}
+            {{ pageDescription || $t('authentication.pageDesc') }}
           </div>
         </div>
       </div>
@@ -57,14 +85,22 @@ const logoSource = computed(() => preferences.logo.source);
       <div class="login-background absolute left-0 top-0 size-full"></div>
       <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%]"
-      />
+      >
+        <template v-if="toolbar" #toolbar>
+          <Toolbar :toolbar-list="toolbarList" />
+        </template>
+      </AuthenticationFormView>
     </div>
 
     <!-- 右侧认证面板 -->
     <AuthenticationFormView
       v-if="authPanelRight"
       class="min-h-full w-2/5 flex-1"
-    />
+    >
+      <template v-if="toolbar" #toolbar>
+        <Toolbar :toolbar-list="toolbarList" />
+      </template>
+    </AuthenticationFormView>
   </div>
 </template>
 

+ 1 - 4
packages/effects/layouts/src/authentication/form.vue

@@ -2,7 +2,6 @@
 import { preferences } from '@vben/preferences';
 
 import { Copyright } from '../basic/copyright';
-import Toolbar from './toolbar.vue';
 
 defineOptions({
   name: 'AuthenticationFormView',
@@ -14,9 +13,7 @@ defineOptions({
     class="flex-col-center bg-background-deep relative px-6 py-10 lg:flex-initial lg:px-8"
   >
     <!-- Toolbar Slot -->
-    <slot name="toolbar">
-      <Toolbar />
-    </slot>
+    <slot name="toolbar"> </slot>
 
     <!-- Router View with Transition and KeepAlive -->
     <RouterView v-slot="{ Component, route }">

+ 24 - 5
packages/effects/layouts/src/authentication/toolbar.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
+import { computed } from 'vue';
+
 import {
   AuthenticationColorToggle,
   AuthenticationLayoutToggle,
@@ -6,22 +8,39 @@ import {
   ThemeToggle,
 } from '../widgets';
 
+interface Props {
+  toolbarList?: ('color' | 'language' | 'layout' | 'theme')[];
+}
+
 defineOptions({
   name: 'AuthenticationToolbar',
 });
+
+const props = withDefaults(defineProps<Props>(), {
+  toolbarList: () => ['color', 'language', 'layout', 'theme'],
+});
+
+const showColor = computed(() => props.toolbarList.includes('color'));
+const showLayout = computed(() => props.toolbarList.includes('layout'));
+const showLanguage = computed(() => props.toolbarList.includes('language'));
+const showTheme = computed(() => props.toolbarList.includes('theme'));
 </script>
 
 <template>
   <div
-    class="flex-center bg-background dark:bg-accent absolute right-2 top-4 rounded-3xl px-3 py-1"
+    :class="{
+      'bg-background dark:bg-accent rounded-3xl px-3 py-1':
+        toolbarList.length > 1,
+    }"
+    class="flex-center absolute right-2 top-4"
   >
     <!-- Only show on medium and larger screens -->
     <div class="hidden md:flex">
-      <AuthenticationColorToggle />
-      <AuthenticationLayoutToggle />
+      <AuthenticationColorToggle v-if="showColor" />
+      <AuthenticationLayoutToggle v-if="showLayout" />
     </div>
     <!-- Always show Language and Theme toggles -->
-    <LanguageToggle />
-    <ThemeToggle />
+    <LanguageToggle v-if="showLanguage" />
+    <ThemeToggle v-if="showTheme" />
   </div>
 </template>

+ 1 - 1
packages/effects/layouts/src/basic/layout.vue

@@ -139,7 +139,7 @@ watch(
   async (val) => {
     if (val) {
       await updateWatermark({
-        content: `${preferences.app.name} 用户名: ${userStore.userInfo?.username}`,
+        content: `${userStore.userInfo?.username}`,
       });
     }
   },

+ 0 - 50
packages/effects/layouts/src/basic/tabbar/use-tab-view-scroll.ts

@@ -1,50 +0,0 @@
-import { ref } from 'vue';
-
-type El = HTMLElement | null | undefined;
-
-export function useTabViewScroll(scrollDistance: number = 150) {
-  const scrollbarEl = ref<El>(null);
-  const scrollViewportEl = ref<El>(null);
-
-  function setScrollBarEl(el: El) {
-    scrollbarEl.value = el;
-  }
-
-  function setScrollViewEl(el: El) {
-    scrollViewportEl.value = el;
-  }
-
-  function getScrollClientWidth() {
-    if (!scrollbarEl.value || !scrollViewportEl.value) return {};
-
-    const scrollbarWidth = scrollbarEl.value.clientWidth;
-    const scrollViewWidth = scrollViewportEl.value.clientWidth;
-
-    return {
-      scrollbarWidth,
-      scrollViewWidth,
-    };
-  }
-
-  function scrollDirection(
-    direction: 'left' | 'right',
-    distance: number = scrollDistance,
-  ) {
-    const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth();
-
-    if (!scrollbarWidth || !scrollViewWidth) return;
-
-    if (scrollbarWidth > scrollViewWidth) return;
-
-    scrollViewportEl.value?.scrollBy({
-      behavior: 'smooth',
-      left: direction === 'left' ? -distance : +distance,
-    });
-  }
-
-  return {
-    scrollDirection,
-    setScrollBarEl,
-    setScrollViewEl,
-  };
-}

+ 0 - 3
packages/effects/layouts/src/iframe/iframe-view.vue

@@ -1,6 +1,3 @@
-<script lang="ts" setup>
-defineOptions({ name: 'IFrameView' });
-</script>
 <template>
   <div></div>
 </template>

+ 24 - 0
pnpm-lock.yaml

@@ -671,6 +671,9 @@ importers:
       '@ctrl/tinycolor':
         specifier: 4.1.0
         version: 4.1.0
+      '@tanstack/vue-store':
+        specifier: ^0.5.5
+        version: 0.5.5(vue@3.4.37(typescript@5.5.4))
       '@vue/shared':
         specifier: ^3.4.37
         version: 3.4.38
@@ -3375,6 +3378,7 @@ packages:
 
   '@ls-lint/ls-lint@2.2.3':
     resolution: {integrity: sha512-ekM12jNm/7O2I/hsRv9HvYkRdfrHpiV1epVuI2NP+eTIcEgdIdKkKCs9KgQydu/8R5YXTov9aHdOgplmCHLupw==}
+    cpu: [x64, arm64, s390x]
     os: [darwin, linux, win32]
     hasBin: true
 
@@ -3823,9 +3827,21 @@ packages:
     peerDependencies:
       tailwindcss: '>=3.0.0 || insiders'
 
+  '@tanstack/store@0.5.5':
+    resolution: {integrity: sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA==}
+
   '@tanstack/virtual-core@3.9.0':
     resolution: {integrity: sha512-Saga7/QRGej/IDCVP5BgJ1oDqlDT2d9rQyoflS3fgMS8ntJ8JGw/LBqK2GorHa06+VrNFc0tGz65XQHJQJetFQ==}
 
+  '@tanstack/vue-store@0.5.5':
+    resolution: {integrity: sha512-j+CDrxVhtQQNOjWzLmCqJeDwmmTAQGvEaNbLr1uPJ9rxJITodJtFNdBFj7l+Nd5o34v2ayEv64Ugh6+1BtuGNg==}
+    peerDependencies:
+      '@vue/composition-api': ^1.2.1
+      vue: 3.4.37
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
   '@tanstack/vue-virtual@3.9.0':
     resolution: {integrity: sha512-MVJhQh57OR3wg2pWL/25IN1/nITFNnpFaz4gOvRCqnxhsH0WRePBBKvixOaFTgiyYfmrjFbb4d0nRMTvsjZZdQ==}
     peerDependencies:
@@ -12638,8 +12654,16 @@ snapshots:
       postcss-selector-parser: 6.0.10
       tailwindcss: 3.4.10
 
+  '@tanstack/store@0.5.5': {}
+
   '@tanstack/virtual-core@3.9.0': {}
 
+  '@tanstack/vue-store@0.5.5(vue@3.4.37(typescript@5.5.4))':
+    dependencies:
+      '@tanstack/store': 0.5.5
+      vue: 3.4.37(typescript@5.5.4)
+      vue-demi: 0.14.10(vue@3.4.37(typescript@5.5.4))
+
   '@tanstack/vue-virtual@3.9.0(vue@3.4.37(typescript@5.5.4))':
     dependencies:
       '@tanstack/virtual-core': 3.9.0