Jelajahi Sumber

feat: add search page

vben 4 tahun lalu
induk
melakukan
dddda5b296
45 mengubah file dengan 1235 tambahan dan 814 penghapusan
  1. 6 0
      CHANGELOG.zh_CN.md
  2. 1 1
      package.json
  3. 7 6
      src/components/Application/index.ts
  4. 55 0
      src/components/Application/src/search/AppSearch.vue
  5. 76 0
      src/components/Application/src/search/AppSearchFooter.vue
  6. 198 0
      src/components/Application/src/search/AppSearchModal.vue
  7. 173 0
      src/components/Application/src/search/useMenuSearch.ts
  8. 3 4
      src/components/Authority/index.ts
  9. 5 6
      src/components/Basic/index.ts
  10. 0 0
      src/components/util.tsx
  11. 20 5
      src/design/transition/fade.less
  12. 3 3
      src/design/transition/zoom.less
  13. 1 0
      src/enums/appEnum.ts
  14. 4 4
      src/hooks/core/useRefs.ts
  15. 3 0
      src/hooks/setting/useHeaderSetting.ts
  16. 0 18
      src/layouts/default/content/index.less
  17. 0 31
      src/layouts/default/content/index.tsx
  18. 62 0
      src/layouts/default/content/index.vue
  19. 5 1
      src/layouts/default/header/LayoutHeader.tsx
  20. 1 0
      src/layouts/default/header/LayoutMultipleHeader.less
  21. 0 0
      src/layouts/default/header/actions/LockAction.less
  22. 0 0
      src/layouts/default/header/actions/LockAction.tsx
  23. 2 1
      src/layouts/default/header/index.less
  24. 2 2
      src/layouts/default/index.tsx
  25. 0 17
      src/layouts/default/lock/index.vue
  26. 8 4
      src/layouts/default/setting/SettingDrawer.tsx
  27. 3 0
      src/layouts/default/setting/enum.ts
  28. 3 0
      src/layouts/default/setting/handler.ts
  29. 1 1
      src/layouts/default/sider/index.tsx
  30. 2 2
      src/layouts/page/index.tsx
  31. 7 0
      src/locales/lang/en/component/app.ts
  32. 1 0
      src/locales/lang/en/layout/header.ts
  33. 1 1
      src/locales/lang/en/layout/setting.ts
  34. 7 0
      src/locales/lang/zh_CN/component/app.ts
  35. 1 0
      src/locales/lang/zh_CN/layout/header.ts
  36. 1 1
      src/locales/lang/zh_CN/layout/setting.ts
  37. 3 3
      src/router/menus/index.ts
  38. 11 1
      src/router/routes/index.ts
  39. 2 2
      src/settings/projectSetting.ts
  40. 2 1
      src/types/config.d.ts
  41. 32 0
      src/utils/factory/createAsyncComponent.tsx
  42. 3 3
      src/utils/helper/treeHelper.ts
  43. 300 0
      src/views/sys/lock/LockPage.vue
  44. 9 292
      src/views/sys/lock/index.vue
  45. 211 404
      yarn.lock

+ 6 - 0
CHANGELOG.zh_CN.md

@@ -1,3 +1,9 @@
+## Wip
+
+### ✨ Features
+
+- 移除左侧菜单搜索,新增顶部菜单搜索功能
+
 ## 2.0.0-rc.13 (2020-12-10)
 
 ## (破坏性更新) Breaking changes

+ 1 - 1
package.json

@@ -23,7 +23,7 @@
   "dependencies": {
     "@iconify/iconify": "^2.0.0-rc.2",
     "@vueuse/core": "^4.0.0-rc.7",
-    "ant-design-vue": "^2.0.0-rc.3",
+    "ant-design-vue": "^2.0.0-rc.4",
     "apexcharts": "^3.22.3",
     "axios": "^0.21.0",
     "crypto-es": "^1.2.6",

+ 7 - 6
src/components/Application/index.ts

@@ -1,10 +1,11 @@
-import AppLocalePicker from './src/AppLocalePicker.vue';
-import AppLogo from './src/AppLogo.vue';
-import AppProvider from './src/AppProvider.vue';
 import { withInstall } from '../util';
+import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
-withInstall(AppLocalePicker, AppLogo, AppProvider);
+export const AppLocalePicker = createAsyncComponent(() => import('./src/AppLocalePicker.vue'));
+export const AppProvider = createAsyncComponent(() => import('./src/AppProvider.vue'));
+export const AppSearch = createAsyncComponent(() => import('./src/search/AppSearch.vue'));
+export const AppLogo = createAsyncComponent(() => import('./src/AppLogo.vue'));
 
-export { useAppProviderContext } from './src/useAppContext';
+withInstall(AppLocalePicker, AppLogo, AppProvider, AppSearch);
 
-export { AppLocalePicker, AppLogo, AppProvider };
+export { useAppProviderContext } from './src/useAppContext';

+ 55 - 0
src/components/Application/src/search/AppSearch.vue

@@ -0,0 +1,55 @@
+<template>
+  <div :class="prefixCls" v-if="getShowSearch" @click="handleSearch">
+    <Tooltip>
+      <template #title> {{ t('component.app.search') }} </template>
+      <SearchOutlined />
+    </Tooltip>
+
+    <transition name="zoom-fade" mode="out-in">
+      <AppSearchModal @close="handleClose" v-if="showModal" />
+    </transition>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { Tooltip } from 'ant-design-vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import AppSearchModal from './AppSearchModal.vue';
+  import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+  import { SearchOutlined } from '@ant-design/icons-vue';
+  import { useI18n } from '/@/hooks/web/useI18n';
+
+  export default defineComponent({
+    name: 'AppSearch',
+    components: { AppSearchModal, Tooltip, SearchOutlined },
+    setup() {
+      const showModal = ref(false);
+      const { prefixCls } = useDesign('app-search');
+      const { getShowSearch } = useHeaderSetting();
+      const { t } = useI18n();
+
+      function handleSearch() {
+        showModal.value = true;
+      }
+      return {
+        t,
+        prefixCls,
+        showModal,
+        getShowSearch,
+        handleClose: () => {
+          showModal.value = false;
+        },
+        handleSearch,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-app-search';
+
+  .@{prefix-cls} {
+    padding: 0 10px;
+  }
+</style>

+ 76 - 0
src/components/Application/src/search/AppSearchFooter.vue

@@ -0,0 +1,76 @@
+<template>
+  <div :class="`${prefixCls}`">
+    <span :class="`${prefixCls}__item`">
+      <g-icon icon="ant-design:enter-outlined" />
+    </span>
+    <span>{{ t('component.app.toSearch') }}</span>
+
+    <span :class="`${prefixCls}__item`">
+      <g-icon icon="bi:arrow-up" />
+    </span>
+    <span :class="`${prefixCls}__item`">
+      <g-icon icon="bi:arrow-down" />
+    </span>
+    <span>{{ t('component.app.toNavigate') }}</span>
+    <span :class="`${prefixCls}__item`">
+      <g-icon icon="mdi:keyboard-esc" />
+    </span>
+    <span>{{ t('component.app.toClose') }}</span>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  export default defineComponent({
+    name: 'AppSearchFooter',
+    components: {},
+    setup() {
+      const { prefixCls } = useDesign('app-search-footer');
+      const { t } = useI18n();
+      return {
+        prefixCls,
+        t,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-app-search-footer';
+
+  .@{prefix-cls} {
+    position: relative;
+    display: flex;
+    height: 44px;
+    padding: 0 16px;
+    font-size: 12px;
+    color: #666;
+    background: rgb(255 255 255);
+    border-radius: 0 0 8px 8px;
+    box-shadow: 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgba(69, 98, 155, 0.12);
+    align-items: center;
+    flex-shrink: 0;
+
+    &__item {
+      display: flex;
+      width: 20px;
+      height: 18px;
+      padding-bottom: 2px;
+      margin-right: 0.4em;
+      background: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
+      border-radius: 2px;
+      box-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff,
+        0 1px 2px 1px rgba(30, 35, 90, 0.4);
+      align-items: center;
+      justify-content: center;
+
+      &:nth-child(2),
+      &:nth-child(3),
+      &:nth-child(6) {
+        margin-left: 14px;
+      }
+    }
+  }
+</style>

+ 198 - 0
src/components/Application/src/search/AppSearchModal.vue

@@ -0,0 +1,198 @@
+<template>
+  <div :class="prefixCls" @click.stop>
+    <ClickOutSide @clickOutside="handleClose">
+      <div :class="`${prefixCls}-content`">
+        <a-input
+          :class="`${prefixCls}-input`"
+          :placeholder="t('component.app.search')"
+          allow-clear
+          @change="handleSearch"
+        >
+          <template #prefix>
+            <SearchOutlined />
+          </template>
+        </a-input>
+        <div :class="`${prefixCls}-not-data`" v-show="getIsNotData">
+          {{ t('component.app.searchNotData') }}
+        </div>
+        <ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">
+          <li
+            :ref="setRefs(index)"
+            v-for="(item, index) in searchResult"
+            :key="item.path"
+            :data-index="index"
+            @mouseenter="handleMouseenter"
+            @click="handleEnter"
+            :class="[
+              `${prefixCls}-list__item`,
+              {
+                [`${prefixCls}-list__item--active`]: activeIndex === index,
+              },
+            ]"
+          >
+            <div :class="`${prefixCls}-list__item-icon`">
+              <g-icon :icon="item.icon || 'mdi:form-select'" :size="20" />
+            </div>
+            <div :class="`${prefixCls}-list__item-text`">{{ item.name }}</div>
+            <div :class="`${prefixCls}-list__item-enter`">
+              <g-icon icon="ant-design:enter-outlined" :size="20" />
+            </div>
+          </li>
+        </ul>
+        <AppSearchFooter />
+      </div>
+    </ClickOutSide>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, computed, unref, ref } from 'vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useRefs } from '/@/hooks/core/useRefs';
+  import { useMenuSearch } from './useMenuSearch';
+  import { SearchOutlined } from '@ant-design/icons-vue';
+  import AppSearchFooter from './AppSearchFooter.vue';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { ClickOutSide } from '/@/components/ClickOutSide';
+  export default defineComponent({
+    name: 'AppSearchModal',
+    components: { SearchOutlined, ClickOutSide, AppSearchFooter },
+    emits: ['close'],
+    setup(_, { emit }) {
+      const scrollWrap = ref<ElRef>(null);
+      const { prefixCls } = useDesign('app-search-modal');
+      const { t } = useI18n();
+      const [refs, setRefs] = useRefs();
+
+      const {
+        handleSearch,
+        searchResult,
+        keyword,
+        activeIndex,
+        handleEnter,
+        handleMouseenter,
+      } = useMenuSearch(refs, scrollWrap, emit);
+
+      const getIsNotData = computed(() => {
+        return !keyword || unref(searchResult).length === 0;
+      });
+
+      return {
+        t,
+        prefixCls,
+        handleSearch,
+        searchResult,
+        activeIndex,
+        getIsNotData,
+        handleEnter,
+        setRefs,
+        scrollWrap,
+        handleMouseenter,
+        handleClose: () => {
+          emit('close');
+        },
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-app-search-modal';
+
+  .@{prefix-cls} {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 100;
+    display: flex;
+    width: 100%;
+    height: 100%;
+    padding-top: 50px;
+    // background: #656c85cc;
+    background: rgba(0, 0, 0, 0.8);
+    justify-content: center;
+    // backdrop-filter: blur(2px);
+
+    &-content {
+      position: relative;
+      width: 532px;
+      // padding: 14px;
+      margin: 0 auto auto auto;
+      background: #f5f6f7;
+      border-radius: 6px;
+      box-shadow: inset 1px 1px 0 0 hsla(0, 0%, 100%, 0.5), 0 3px 8px 0 #555a64;
+      flex-direction: column;
+    }
+
+    &-input {
+      width: calc(100% - 28px);
+      height: 56px;
+      margin: 14px 14px 0 14px;
+      font-size: 1.5em;
+      color: #1c1e21;
+
+      span[role='img'] {
+        color: #999;
+      }
+    }
+
+    &-not-data {
+      display: flex;
+      width: 100%;
+      height: 100px;
+      font-size: 0.9;
+      color: rgb(150 159 175);
+      align-items: center;
+      justify-content: center;
+    }
+
+    &-list {
+      max-height: 472px;
+      padding: 0 14px;
+      padding-bottom: 20px;
+      margin: 0 auto;
+      margin-top: 14px;
+      overflow: auto;
+
+      &__item {
+        position: relative;
+        display: flex;
+        width: 100%;
+        height: 56px;
+        padding-bottom: 4px;
+        padding-left: 14px;
+        margin-top: 8px;
+        font-size: 14px;
+        color: @text-color-base;
+        cursor: pointer;
+        // background: @primary-color;
+        background: #fff;
+        border-radius: 4px;
+        box-shadow: 0 1px 3px 0 #d4d9e1;
+        align-items: center;
+
+        &--active {
+          color: #fff;
+          background: @primary-color;
+
+          .@{prefix-cls}-list__item-enter {
+            opacity: 1;
+          }
+        }
+
+        &-icon {
+          width: 30px;
+        }
+
+        &-text {
+          flex: 1;
+        }
+
+        &-enter {
+          width: 30px;
+          opacity: 0;
+        }
+      }
+    }
+  }
+</style>

+ 173 - 0
src/components/Application/src/search/useMenuSearch.ts

@@ -0,0 +1,173 @@
+import { cloneDeep } from 'lodash-es';
+import { ref, onBeforeUnmount, onBeforeMount, unref, Ref } from 'vue';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { getMenus } from '/@/router/menus';
+import type { Menu } from '/@/router/types';
+import { filter, forEach } from '/@/utils/helper/treeHelper';
+import { useDebounce } from '/@/hooks/core/useDebounce';
+import { useGo } from '/@/hooks/web/usePage';
+import { useScrollTo } from '/@/hooks/event/useScrollTo';
+
+export interface SearchResult {
+  name: string;
+  path: string;
+  icon?: string;
+}
+
+const enum KeyCodeEnum {
+  UP = 38,
+  DOWN = 40,
+  ENTER = 13,
+  ESC = 27,
+}
+
+// Translate special characters
+function transform(c: string) {
+  const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|'];
+  return code.includes(c) ? `\\${c}` : c;
+}
+
+function createSearchReg(key: string) {
+  const keys = [...key].map((item) => transform(item));
+  const str = ['', ...keys, ''].join('.*');
+  return new RegExp(str);
+}
+
+export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>, emit: EmitType) {
+  const searchResult = ref<SearchResult[]>([]);
+  const keyword = ref('');
+  const activeIndex = ref(-1);
+
+  let menuList: Menu[] = [];
+
+  const { t } = useI18n();
+  const go = useGo();
+  const [handleSearch] = useDebounce(search, 200);
+
+  onBeforeMount(async () => {
+    const list = await getMenus();
+    menuList = cloneDeep(list);
+    forEach(menuList, (item) => {
+      item.name = t(item.name);
+    });
+
+    document.addEventListener('keydown', registerKeyDown);
+  });
+
+  onBeforeUnmount(() => {
+    document.removeEventListener('keydown', registerKeyDown);
+  });
+
+  function search(e: ChangeEvent) {
+    e?.stopPropagation();
+    const key = e.target.value;
+    keyword.value = key.trim();
+    if (!key) {
+      searchResult.value = [];
+      return;
+    }
+    const reg = createSearchReg(unref(keyword));
+    const filterMenu = filter(menuList, (item) => {
+      return reg.test(item.name);
+    });
+    searchResult.value = handlerSearchResult(filterMenu, reg);
+    activeIndex.value = 0;
+  }
+
+  function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
+    const ret: SearchResult[] = [];
+
+    filterMenu.forEach((item) => {
+      const { name, path, icon, children } = item;
+      if (reg.test(name) && !children?.length) {
+        ret.push({
+          name: parent?.name ? `${parent.name} > ${name}` : name,
+          path,
+          icon,
+        });
+      }
+      if (Array.isArray(children) && children.length) {
+        ret.push(...handlerSearchResult(children, reg, item));
+      }
+    });
+    return ret;
+  }
+
+  function handleMouseenter(e: ChangeEvent) {
+    const index = e.target.dataset.index;
+    activeIndex.value = Number(index);
+  }
+
+  function handleUp() {
+    if (!searchResult.value.length) return;
+    activeIndex.value--;
+    if (activeIndex.value < 0) {
+      activeIndex.value = searchResult.value.length - 1;
+    }
+    handleScroll();
+  }
+
+  function handleDown() {
+    if (!searchResult.value.length) return;
+    activeIndex.value++;
+    if (activeIndex.value > searchResult.value.length - 1) {
+      activeIndex.value = 0;
+    }
+    handleScroll();
+  }
+
+  function handleScroll() {
+    const refList = unref(refs);
+    if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) return;
+
+    const index = unref(activeIndex);
+    const currentRef = refList[index];
+    if (!currentRef) return;
+    const wrapEl = unref(scrollWrap);
+    if (!wrapEl) return;
+    const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight;
+    const wrapHeight = wrapEl.offsetHeight;
+    const { start } = useScrollTo({
+      el: wrapEl,
+      duration: 100,
+      to: scrollHeight - wrapHeight,
+    });
+    start();
+  }
+
+  function handleEnter() {
+    if (!searchResult.value.length) return;
+    const result = unref(searchResult);
+    const index = unref(activeIndex);
+    if (result.length === 0 || index < 0) {
+      return;
+    }
+    const to = result[index];
+    handleClose();
+    go(to.path);
+  }
+
+  function handleClose() {
+    emit('close');
+  }
+
+  function registerKeyDown(e: KeyboardEvent) {
+    const keyCode = window.event ? e.keyCode : e.which;
+    switch (keyCode) {
+      case KeyCodeEnum.UP:
+        handleUp();
+        break;
+      case KeyCodeEnum.DOWN:
+        handleDown();
+        break;
+      case KeyCodeEnum.ENTER:
+        handleEnter();
+        break;
+      case KeyCodeEnum.ESC:
+        handleClose();
+        break;
+    }
+  }
+
+  return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter };
+}

+ 3 - 4
src/components/Authority/index.ts

@@ -1,7 +1,6 @@
-import Authority from './src/index.vue';
-
 import { withInstall } from '../util';
+import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
-withInstall(Authority);
+export const Authority = createAsyncComponent(() => import('./src/index.vue'));
 
-export { Authority };
+withInstall(Authority);

+ 5 - 6
src/components/Basic/index.ts

@@ -1,9 +1,8 @@
-import BasicArrow from './src/BasicArrow.vue';
-import BasicHelp from './src/BasicHelp.vue';
-import BasicTitle from './src/BasicTitle.vue';
-
 import { withInstall } from '../util';
+import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
-withInstall(BasicArrow, BasicHelp, BasicTitle);
+export const BasicArrow = createAsyncComponent(() => import('./src/BasicArrow.vue'));
+export const BasicHelp = createAsyncComponent(() => import('./src/BasicHelp.vue'));
+export const BasicTitle = createAsyncComponent(() => import('./src/BasicTitle.vue'));
 
-export { BasicArrow, BasicHelp, BasicTitle };
+withInstall(BasicArrow, BasicHelp, BasicTitle);

+ 0 - 0
src/components/util.ts → src/components/util.tsx


+ 20 - 5
src/design/transition/fade.less

@@ -31,18 +31,33 @@
 // Speed: 1x
 .fade-bottom-enter-active,
 .fade-bottom-leave-active {
-  transition: opacity 0.2s, transform 0.25s;
+  transition: opacity 0.25s, transform 0.3s;
 }
 
-.fade-bottom-enter-from,
-.fade-bottom-enter {
+.fade-bottom-enter-from {
   opacity: 0;
-  transform: translateY(-8%);
+  transform: translateY(-10%);
 }
 
 .fade-bottom-leave-to {
   opacity: 0;
-  transform: translateY(8%);
+  transform: translateY(10%);
+}
+
+// fade-scale
+.fade-scale-leave-active,
+.fade-scale-enter-active {
+  transition: all 0.28s;
+}
+
+.fade-scale-enter-from {
+  opacity: 0;
+  transform: scale(1.2);
+}
+
+.fade-scale-leave-to {
+  opacity: 0;
+  transform: scale(0.8);
 }
 
 // ///////////////////////////////////////////////

+ 3 - 3
src/design/transition/zoom.less

@@ -13,15 +13,15 @@
 // zoom-fade
 .zoom-fade-enter-active,
 .zoom-fade-leave-active {
-  transition: transform 0.15s, opacity 0.2s ease-out;
+  transition: transform 0.2s, opacity 0.3s ease-out;
 }
 
 .zoom-fade-enter-from {
   opacity: 0;
-  transform: scale(0.97);
+  transform: scale(0.92);
 }
 
 .zoom-fade-leave-to {
   opacity: 0;
-  transform: scale(1.03);
+  transform: scale(1.06);
 }

+ 1 - 0
src/enums/appEnum.ts

@@ -39,4 +39,5 @@ export enum RouterTransitionEnum {
   FADE_SIDE = 'fade-slide',
   FADE = 'fade',
   FADE_BOTTOM = 'fade-bottom',
+  FADE_SCALE = 'fade-scale',
 }

+ 4 - 4
src/hooks/core/useRefs.ts

@@ -1,13 +1,13 @@
-import { ref, onBeforeUpdate } from 'vue';
+import { ref, onBeforeUpdate, Ref } from 'vue';
 
-export function useRefs() {
-  const refs = ref([] as Element[]);
+export function useRefs(): [Ref<HTMLElement[]>, (index: number) => (el: HTMLElement) => void] {
+  const refs = ref<HTMLElement[]>([]);
 
   onBeforeUpdate(() => {
     refs.value = [];
   });
 
-  const setRefs = (index: number) => (el: Element) => {
+  const setRefs = (index: number) => (el: HTMLElement) => {
     refs.value[index] = el;
   };
 

+ 3 - 0
src/hooks/setting/useHeaderSetting.ts

@@ -51,6 +51,8 @@ const getFixed = computed(() => unref(getHeaderSetting).fixed);
 
 const getHeaderBgColor = computed(() => unref(getHeaderSetting).bgColor);
 
+const getShowSearch = computed(() => unref(getHeaderSetting).showSearch);
+
 const getShowRedo = computed(() => unref(getHeaderSetting).showRedo && unref(getShowMultipleTab));
 
 const getUseLockPage = computed(() => unref(getHeaderSetting).useLockPage);
@@ -87,6 +89,7 @@ export function useHeaderSetting() {
     getHeaderSetting,
 
     getShowDoc,
+    getShowSearch,
     getHeaderTheme,
     getShowRedo,
     getUseLockPage,

+ 0 - 18
src/layouts/default/content/index.less

@@ -1,18 +0,0 @@
-@import (reference) '../../../design/index.less';
-
-.layout-content {
-  position: relative;
-  flex: 1 1 auto;
-  min-height: 0;
-
-  &.fixed {
-    width: 1200px;
-    margin: 0 auto;
-  }
-
-  &__loading {
-    position: absolute;
-    top: 200px;
-    z-index: @page-loading-z-index;
-  }
-}

+ 0 - 31
src/layouts/default/content/index.tsx

@@ -1,31 +0,0 @@
-import './index.less';
-
-import { defineComponent, unref } from 'vue';
-import { Loading } from '/@/components/Loading';
-
-import { useRootSetting } from '/@/hooks/setting/useRootSetting';
-import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
-import PageLayout from '/@/layouts/page/index';
-export default defineComponent({
-  name: 'LayoutContent',
-  setup() {
-    const { getOpenPageLoading } = useTransitionSetting();
-    const { getLayoutContentMode, getPageLoading } = useRootSetting();
-
-    return () => {
-      return (
-        <div class={['layout-content', unref(getLayoutContentMode)]}>
-          {unref(getOpenPageLoading) && (
-            <Loading
-              loading={unref(getPageLoading)}
-              background="rgba(240, 242, 245, 0.6)"
-              absolute
-              class="layout-content__loading"
-            />
-          )}
-          <PageLayout />
-        </div>
-      );
-    };
-  },
-});

+ 62 - 0
src/layouts/default/content/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <div :class="[prefixCls, getLayoutContentMode]">
+    <transition name="fade">
+      <Loading
+        v-if="getOpenPageLoading"
+        :loading="getPageLoading"
+        background="rgba(240, 242, 245, 0.6)"
+        absolute
+        :class="`${prefixCls}__loading`"
+      />
+    </transition>
+    <PageLayout />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+  import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
+  import PageLayout from '/@/layouts/page/index';
+  import { Loading } from '/@/components/Loading';
+  import Transition from '/@/views/demo/comp/lazy/Transition.vue';
+
+  export default defineComponent({
+    name: 'LayoutContent',
+    components: { PageLayout, Loading, Transition },
+    setup() {
+      const { prefixCls } = useDesign('layout-content');
+      const { getOpenPageLoading } = useTransitionSetting();
+      const { getLayoutContentMode, getPageLoading } = useRootSetting();
+
+      return {
+        prefixCls,
+        getOpenPageLoading,
+        getLayoutContentMode,
+        getPageLoading,
+      };
+    },
+  });
+</script>
+<style lang="less">
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-layout-content';
+
+  .@{prefix-cls} {
+    position: relative;
+    flex: 1 1 auto;
+    min-height: 0;
+
+    &.fixed {
+      width: 1200px;
+      margin: 0 auto;
+    }
+
+    &__loading {
+      position: absolute;
+      top: 200px;
+      z-index: @page-loading-z-index;
+    }
+  }
+</style>

+ 5 - 1
src/layouts/default/header/LayoutHeader.tsx

@@ -18,7 +18,7 @@ import { AppLogo } from '/@/components/Application';
 import UserDropdown from './UserDropdown';
 import LayoutMenu from '../menu';
 import LayoutBreadcrumb from './LayoutBreadcrumb.vue';
-import LockAction from '../lock/LockAction';
+import LockAction from './actions/LockAction';
 import LayoutTrigger from '../LayoutTrigger';
 import NoticeAction from './notice/NoticeActionItem.vue';
 import {
@@ -28,6 +28,8 @@ import {
   LockOutlined,
   BugOutlined,
 } from '@ant-design/icons-vue';
+
+import { AppSearch } from '/@/components/Application';
 import { useModal } from '/@/components/Modal';
 
 import { useFullscreen } from '/@/hooks/web/useFullScreen';
@@ -200,6 +202,8 @@ export default defineComponent({
     function renderAction() {
       return (
         <div class={`layout-header__action`}>
+          {unref(isPc) && <AppSearch class="layout-header__action-item" />}
+
           {unref(getUseErrorHandle) && unref(isPc) && (
             <TooltipItem title={t('layout.header.tooltipErrorLog')}>
               {() => (

+ 1 - 0
src/layouts/default/header/LayoutMultipleHeader.less

@@ -1,5 +1,6 @@
 .multiple-tab-header {
   flex: 0 0 auto;
+  transition: width 0.2s;
 
   &.dark {
     margin-left: -1px;

+ 0 - 0
src/layouts/default/lock/LockAction.less → src/layouts/default/header/actions/LockAction.less


+ 0 - 0
src/layouts/default/lock/LockAction.tsx → src/layouts/default/header/actions/LockAction.tsx


+ 2 - 1
src/layouts/default/header/index.less

@@ -131,7 +131,8 @@
         }
       }
 
-      &-icon {
+      &-icon,
+      span[role='img'] {
         color: @text-color-base;
       }
     }

+ 2 - 2
src/layouts/default/index.tsx

@@ -4,9 +4,9 @@ import { defineComponent, unref, computed, ref } from 'vue';
 import { Layout, BackTop } from 'ant-design-vue';
 import LayoutHeader from './header/LayoutHeader';
 
-import LayoutContent from './content';
+import LayoutContent from './content/index.vue';
 import LayoutFooter from './footer';
-import LayoutLockPage from './lock/index.vue';
+import LayoutLockPage from '/@/views/sys/lock/index.vue';
 import LayoutSideBar from './sider';
 import SettingBtn from './setting/index.vue';
 import LayoutMultipleHeader from './header/LayoutMultipleHeader';

+ 0 - 17
src/layouts/default/lock/index.vue

@@ -1,17 +0,0 @@
-<template>
-  <transition name="fade-bottom">
-    <LockPage v-if="getIsLock" />
-  </transition>
-</template>
-<script lang="ts">
-  import { defineComponent } from 'vue';
-  import LockPage from '/@/views/sys/lock/index.vue';
-  import { getIsLock } from '/@/hooks/web/useLockPage';
-  export default defineComponent({
-    name: 'LayoutLockPage',
-    components: { LockPage },
-    setup() {
-      return { getIsLock };
-    },
-  });
-</script>

+ 8 - 4
src/layouts/default/setting/SettingDrawer.tsx

@@ -204,7 +204,6 @@ export default defineComponent({
       getCollapsedShowTitle,
       getMenuFixed,
       getCollapsed,
-      getShowSearch,
       getCanDrag,
       getTopMenuAlign,
       getAccordion,
@@ -214,7 +213,12 @@ export default defineComponent({
       getSplit,
     } = useMenuSetting();
 
-    const { getShowHeader, getFixed: getHeaderFixed, getHeaderBgColor } = useHeaderSetting();
+    const {
+      getShowHeader,
+      getFixed: getHeaderFixed,
+      getHeaderBgColor,
+      getShowSearch,
+    } = useHeaderSetting();
 
     const { getShowMultipleTab, getShowQuick } = useMultipleTabSetting();
 
@@ -274,10 +278,10 @@ export default defineComponent({
         }),
         renderSwitchItem(t('layout.setting.menuSearch'), {
           handler: (e) => {
-            baseHandler(HandlerEnum.MENU_SHOW_SEARCH, e);
+            baseHandler(HandlerEnum.HEADER_SEARCH, e);
           },
           def: unref(getShowSearch),
-          disabled: !unref(getShowMenuRef),
+          disabled: !unref(getShowHeader),
         }),
         renderSwitchItem(t('layout.setting.menuAccordion'), {
           handler: (e) => {

+ 3 - 0
src/layouts/default/setting/enum.ts

@@ -28,6 +28,8 @@ export enum HandlerEnum {
   HEADER_THEME,
   HEADER_FIXED,
 
+  HEADER_SEARCH,
+
   TABS_SHOW_QUICK,
   TABS_SHOW,
 
@@ -94,6 +96,7 @@ export const routerTransitionOptions = [
   RouterTransitionEnum.ZOOM_OUT,
   RouterTransitionEnum.FADE_SIDE,
   RouterTransitionEnum.FADE_BOTTOM,
+  RouterTransitionEnum.FADE_SCALE,
 ].map((item) => {
   return {
     label: item,

+ 3 - 0
src/layouts/default/setting/handler.ts

@@ -119,6 +119,9 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf
       updateHeaderBgColor(value);
       return { headerSetting: { bgColor: value } };
 
+    case HandlerEnum.HEADER_SEARCH:
+      return { headerSetting: { showSearch: value } };
+
     case HandlerEnum.HEADER_FIXED:
       return { headerSetting: { fixed: value } };
 

+ 1 - 1
src/layouts/default/sider/index.tsx

@@ -100,7 +100,7 @@ export default defineComponent({
           flex: `0 0 ${width}`,
           maxWidth: width,
           minWidth: width,
-          transition: 'all 0.15s',
+          transition: 'all 0.2s',
         };
       }
     );

+ 2 - 2
src/layouts/page/index.tsx

@@ -30,7 +30,7 @@ export default defineComponent({
 
     return () => {
       return (
-        <>
+        <div>
           <RouterView>
             {{
               default: ({ Component, route }: DefaultContext) => {
@@ -65,7 +65,7 @@ export default defineComponent({
             }}
           </RouterView>
           {unref(getCanEmbedIFramePage) && <FrameLayout />}
-        </>
+        </div>
       );
     };
   },

+ 7 - 0
src/locales/lang/en/component/app.ts

@@ -0,0 +1,7 @@
+export default {
+  search: 'Search',
+  searchNotData: 'No search results yet',
+  toSearch: 'to search',
+  toNavigate: 'to navigate',
+  toClose: 'to close',
+};

+ 1 - 0
src/locales/lang/en/layout/header.ts

@@ -3,6 +3,7 @@ export default {
   dropdownItemDoc: 'Document',
   dropdownItemLoginOut: 'Login Out',
 
+  search: 'Search',
   tooltipErrorLog: 'Error log',
   tooltipLock: 'Lock screen',
   tooltipNotify: 'Notification',

+ 1 - 1
src/locales/lang/en/layout/setting.ts

@@ -40,7 +40,7 @@ export default {
   sidebarTheme: 'Menu theme',
 
   menuDrag: 'Drag Sidebar',
-  menuSearch: 'Sidebar search',
+  menuSearch: 'Menu search',
   menuAccordion: 'Sidebar accordion',
   menuCollapse: 'Collapse menu',
   collapseMenuDisplayName: 'Collapse menu display name',

+ 7 - 0
src/locales/lang/zh_CN/component/app.ts

@@ -0,0 +1,7 @@
+export default {
+  search: '搜索',
+  searchNotData: '暂无搜索结果',
+  toSearch: '确认',
+  toNavigate: '切换',
+  toClose: '关闭',
+};

+ 1 - 0
src/locales/lang/zh_CN/layout/header.ts

@@ -4,6 +4,7 @@ export default {
   dropdownItemLoginOut: '退出系统',
 
   // tooltip
+  search: '搜索',
   tooltipErrorLog: '错误日志',
   tooltipLock: '锁定屏幕',
   tooltipNotify: '消息通知',

+ 1 - 1
src/locales/lang/zh_CN/layout/setting.ts

@@ -39,7 +39,7 @@ export default {
   sidebarTheme: '菜单主题',
 
   menuDrag: '侧边菜单拖拽',
-  menuSearch: '侧边菜单搜索',
+  menuSearch: '菜单搜索',
   menuAccordion: '侧边菜单手风琴模式',
   menuCollapse: '折叠菜单',
   collapseMenuDisplayName: '折叠菜单显示名称',

+ 3 - 3
src/router/menus/index.ts

@@ -45,13 +45,13 @@ async function getAsyncMenus() {
 }
 
 // 获取深层扁平化菜单
-export const getFlatMenus = async () => {
+export const getFlatMenus = async (): Promise<Menu[]> => {
   const menus = await getAsyncMenus();
   return flatMenus(menus);
 };
 
 // 获取菜单 树级
-export const getMenus = async () => {
+export const getMenus = async (): Promise<Menu[]> => {
   const menus = await getAsyncMenus();
   const routes = router.getRoutes();
   return !isBackMode() ? filter(menus, basicFilter(routes)) : menus;
@@ -65,7 +65,7 @@ export async function getCurrentParentPath(currentPath: string) {
 }
 
 // 获取1级菜单,删除children
-export async function getShallowMenus() {
+export async function getShallowMenus(): Promise<Menu[]> {
   const menus = await getAsyncMenus();
   const routes = router.getRoutes();
   const shallowMenuList = menus.map((item) => ({ ...item, children: undefined }));

+ 11 - 1
src/router/routes/index.ts

@@ -3,6 +3,7 @@ import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types';
 import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '../constant';
 
 import modules from 'globby!/@/router/routes/modules/**/*.@(ts)';
+import { PageEnum } from '/@/enums/pageEnum';
 
 import { t } from '/@/hooks/web/useI18n';
 
@@ -15,6 +16,15 @@ Object.keys(modules).forEach((key) => {
 
 export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList];
 
+export const RootRoute: AppRouteRecordRaw = {
+  path: '/',
+  name: 'Root',
+  redirect: PageEnum.BASE_HOME,
+  meta: {
+    title: 'Root',
+  },
+};
+
 export const LoginRoute: AppRouteRecordRaw = {
   path: '/login',
   name: 'Login',
@@ -25,4 +35,4 @@ export const LoginRoute: AppRouteRecordRaw = {
 };
 
 // 基础路由 不用权限
-export const basicRoutes = [LoginRoute, REDIRECT_ROUTE];
+export const basicRoutes = [LoginRoute, RootRoute, REDIRECT_ROUTE];

+ 2 - 2
src/settings/projectSetting.ts

@@ -70,6 +70,8 @@ const setting: ProjectConfig = {
     showDoc: true,
     // Whether to show the notification button
     showNotice: true,
+    // Whether to display the menu search
+    showSearch: true,
   },
 
   // Menu configuration
@@ -101,8 +103,6 @@ const setting: ProjectConfig = {
     split: false,
     // Top menu layout
     topMenuAlign: 'center',
-    // Hide the search box when the menu is collapsed
-    collapsedShowSearch: false,
     // Fold trigger position
     trigger: TriggerEnum.HEADER,
     // Turn on accordion mode, only show a menu

+ 2 - 1
src/types/config.d.ts

@@ -17,7 +17,6 @@ export interface MenuSetting {
   type: MenuTypeEnum;
   theme: ThemeEnum;
   topMenuAlign: 'start' | 'center' | 'end';
-  collapsedShowSearch: boolean;
   trigger: TriggerEnum;
   accordion: boolean;
 }
@@ -45,6 +44,8 @@ export interface HeaderSetting {
   showDoc: boolean;
   // 显示消息中心按钮
   showNotice: boolean;
+
+  showSearch: boolean;
 }
 
 export interface LocaleSetting {

+ 32 - 0
src/utils/factory/createAsyncComponent.tsx

@@ -0,0 +1,32 @@
+import { defineAsyncComponent } from 'vue';
+import { Spin } from 'ant-design-vue';
+
+export function createAsyncComponent(loader: any) {
+  return defineAsyncComponent({
+    loader: loader,
+    loadingComponent: <Spin spinning={true} />,
+    // The error component will be displayed if a timeout is
+    // provided and exceeded. Default: Infinity.
+    timeout: 3000,
+    // Defining if component is suspensible. Default: true.
+    // suspensible: false,
+    delay: 100,
+    /**
+     *
+     * @param {*} error Error message object
+     * @param {*} retry A function that indicating whether the async component should retry when the loader promise rejects
+     * @param {*} fail  End of failure
+     * @param {*} attempts Maximum allowed retries number
+     */
+    onError(error, retry, fail, attempts) {
+      if (error.message.match(/fetch/) && attempts <= 3) {
+        // retry on fetch errors, 3 max attempts
+        retry();
+      } else {
+        // Note that retry/fail are like resolve/reject of a promise:
+        // one of them must be called for the error handling to continue.
+        fail();
+      }
+    },
+  });
+}

+ 3 - 3
src/utils/helper/treeHelper.ts

@@ -124,7 +124,7 @@ export function filter<T = any>(
   tree: T[],
   func: (n: T) => boolean,
   config: Partial<TreeHelperConfig> = {}
-) {
+): T[] {
   config = getConfig(config);
   const children = config.children as string;
   function listFilter(list: T[]) {
@@ -142,7 +142,7 @@ export function forEach<T = any>(
   tree: T[],
   func: (n: T) => any,
   config: Partial<TreeHelperConfig> = {}
-) {
+): void {
   config = getConfig(config);
   const list: any[] = [...tree];
   const { children } = config;
@@ -155,7 +155,7 @@ export function forEach<T = any>(
 /**
  * @description: 提取tree指定结构
  */
-export function treeMap(treeData: any[], opt: { children?: string; conversion: Fn }) {
+export function treeMap<T = any>(treeData: T[], opt: { children?: string; conversion: Fn }): T[] {
   return treeData.map((item) => treeMapEach(item, opt));
 }
 

+ 300 - 0
src/views/sys/lock/LockPage.vue

@@ -0,0 +1,300 @@
+<template>
+  <div :class="prefixCls">
+    <div :class="`${prefixCls}__unlock`" @click="handleShowForm(false)" v-show="showDate">
+      <LockOutlined />
+      <span>{{ t('sys.lock.unlock') }}</span>
+    </div>
+
+    <div :class="`${prefixCls}__date`">
+      <div :class="`${prefixCls}__hour`">
+        {{ hour }}
+        <span class="meridiem" v-show="showDate">{{ meridiem }}</span>
+      </div>
+      <div :class="`${prefixCls}__minute`">{{ minute }} </div>
+    </div>
+    <transition name="fade-slide">
+      <div :class="`${prefixCls}-entry`" v-show="!showDate">
+        <div :class="`${prefixCls}-entry-content`">
+          <div :class="`${prefixCls}-entry__header`">
+            <img src="/@/assets/images/header.jpg" :class="`${prefixCls}-entry__header-img`" />
+            <p :class="`${prefixCls}-entry__header-name`">{{ realName }}</p>
+          </div>
+          <InputPassword :placeholder="t('sys.lock.placeholder')" v-model:value="password" />
+          <span :class="`${prefixCls}-entry__err-msg`" v-if="errMsgRef">
+            {{ t('sys.lock.alert') }}
+          </span>
+          <div :class="`${prefixCls}-entry__footer`">
+            <a-button
+              type="link"
+              size="small"
+              class="mt-2 mr-2"
+              :disabled="loadingRef"
+              @click="handleShowForm(true)"
+            >
+              {{ t('sys.lock.back') }}
+            </a-button>
+            <a-button
+              type="link"
+              size="small"
+              class="mt-2 mr-2"
+              :disabled="loadingRef"
+              @click="goLogin"
+            >
+              {{ t('sys.lock.backToLogin') }}
+            </a-button>
+            <a-button class="mt-2" type="link" size="small" @click="unLock()" :loading="loadingRef">
+              {{ t('sys.lock.entry') }}
+            </a-button>
+          </div>
+        </div>
+      </div>
+    </transition>
+
+    <div :class="`${prefixCls}__footer-date`">
+      <div class="time" v-show="!showDate">
+        {{ hour }}:{{ minute }} <span class="meridiem">{{ meridiem }}</span>
+      </div>
+      <div class="date"> {{ year }}/{{ month }}/{{ day }} {{ week }} </div>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, computed } from 'vue';
+  import { Alert, Input } from 'ant-design-vue';
+
+  import { userStore } from '/@/store/modules/user';
+  import { lockStore } from '/@/store/modules/lock';
+  import { useI18n } from '/@/hooks/web/useI18n';
+
+  import { useNow } from './useNow';
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import { LockOutlined } from '@ant-design/icons-vue';
+
+  export default defineComponent({
+    name: 'LockPage',
+    components: { Alert, LockOutlined, InputPassword: Input.Password },
+
+    setup() {
+      const passwordRef = ref('');
+      const loadingRef = ref(false);
+      const errMsgRef = ref(false);
+      const showDate = ref(true);
+
+      const { prefixCls } = useDesign('lock-page');
+
+      const { start, stop, ...state } = useNow(true);
+
+      const { t } = useI18n();
+
+      const realName = computed(() => {
+        const { realName } = userStore.getUserInfoState || {};
+        return realName;
+      });
+
+      /**
+       * @description: unLock
+       */
+      async function unLock() {
+        if (!passwordRef.value) {
+          return;
+        }
+        let password = passwordRef.value;
+        try {
+          loadingRef.value = true;
+          const res = await lockStore.unLockAction({ password });
+          errMsgRef.value = !res;
+        } finally {
+          loadingRef.value = false;
+        }
+      }
+
+      function goLogin() {
+        userStore.loginOut(true);
+        lockStore.resetLockInfo();
+      }
+
+      function handleShowForm(show = false) {
+        showDate.value = show;
+      }
+
+      return {
+        goLogin,
+        realName,
+        unLock,
+        errMsgRef,
+        loadingRef,
+        t,
+        prefixCls,
+        showDate,
+        password: passwordRef,
+        handleShowForm,
+        ...state,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-lock-page';
+
+  .@{prefix-cls} {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 3000;
+    display: flex;
+    width: 100vw;
+    height: 100vh;
+    // background: rgba(23, 27, 41);
+    background: #000;
+    align-items: center;
+    justify-content: center;
+
+    &__unlock {
+      position: absolute;
+      top: 0;
+      left: 50%;
+      display: flex;
+      height: 50px;
+      padding-top: 20px;
+      font-size: 18px;
+      color: #fff;
+      cursor: pointer;
+      transform: translate(-50%, 0);
+      flex-direction: column;
+      align-items: center;
+      justify-content: space-between;
+      transition: all 0.3s;
+    }
+
+    &__date {
+      display: flex;
+      width: 100vw;
+      height: 100vh;
+      align-items: center;
+      justify-content: center;
+    }
+
+    &__hour {
+      position: relative;
+      margin-right: 80px;
+
+      .meridiem {
+        position: absolute;
+        top: 20px;
+        left: 20px;
+        font-size: 26px;
+      }
+      @media (max-width: @screen-xs) {
+        margin-right: 20px;
+      }
+    }
+
+    &__hour,
+    &__minute {
+      display: flex;
+      width: 40%;
+      height: 74%;
+      // font-size: 50em;
+      font-weight: 700;
+      color: #bababa;
+      background: #141313;
+      border-radius: 30px;
+      justify-content: center;
+      align-items: center;
+      // .respond-to(large-only, { font-size: 25em;});
+      // .respond-to(large-only, { font-size: 30em;});
+      @media (min-width: @screen-xxxl-min) {
+        font-size: 46em;
+      }
+      @media (min-width: @screen-xl-max) and (max-width: @screen-xxl-max) {
+        font-size: 38em;
+      }
+
+      @media (min-width: @screen-lg-max) and (max-width: @screen-xl-max) {
+        font-size: 30em;
+      }
+      @media (min-width: @screen-md-max) and (max-width: @screen-lg-max) {
+        font-size: 23em;
+      }
+      @media (min-width: @screen-sm-max) and (max-width: @screen-md-max) {
+        font-size: 19em;
+      }
+      @media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) {
+        font-size: 13em;
+      }
+      @media (max-width: @screen-xs) {
+        height: 50%;
+        font-size: 6em;
+        border-radius: 20px;
+      }
+    }
+
+    &__footer-date {
+      position: absolute;
+      bottom: 20px;
+      left: 50%;
+      font-family: helvetica;
+      color: #bababa;
+      transform: translate(-50%, 0);
+
+      .time {
+        font-size: 50px;
+
+        .meridiem {
+          font-size: 32px;
+        }
+      }
+
+      .date {
+        font-size: 26px;
+      }
+    }
+
+    &-entry {
+      position: absolute;
+      top: 0;
+      left: 0;
+      display: flex;
+      width: 100%;
+      height: 100%;
+      background: rgba(0, 0, 0, 0.5);
+      backdrop-filter: blur(8px);
+      justify-content: center;
+      align-items: center;
+
+      &-content {
+        width: 260px;
+      }
+
+      &__header {
+        text-align: center;
+
+        &-img {
+          width: 70px;
+          border-radius: 50%;
+        }
+
+        &-name {
+          margin-top: 5px;
+          font-weight: 500;
+          color: #bababa;
+        }
+      }
+
+      &__err-msg {
+        display: inline-block;
+        margin-top: 10px;
+        color: @error-color;
+      }
+
+      &__footer {
+        display: flex;
+        justify-content: space-between;
+      }
+    }
+  }
+</style>

+ 9 - 292
src/views/sys/lock/index.vue

@@ -1,300 +1,17 @@
 <template>
-  <div :class="prefixCls">
-    <div :class="`${prefixCls}__unlock`" @click="handleShowForm(false)" v-show="showDate">
-      <LockOutlined />
-      <span>{{ t('sys.lock.unlock') }}</span>
-    </div>
-
-    <div :class="`${prefixCls}__date`">
-      <div :class="`${prefixCls}__hour`">
-        {{ hour }}
-        <span class="meridiem" v-show="showDate">{{ meridiem }}</span>
-      </div>
-      <div :class="`${prefixCls}__minute`">{{ minute }} </div>
-    </div>
-    <transition name="fade-slide">
-      <div :class="`${prefixCls}-entry`" v-show="!showDate">
-        <div :class="`${prefixCls}-entry-content`">
-          <div :class="`${prefixCls}-entry__header`">
-            <img src="/@/assets/images/header.jpg" :class="`${prefixCls}-entry__header-img`" />
-            <p :class="`${prefixCls}-entry__header-name`">{{ realName }}</p>
-          </div>
-          <InputPassword :placeholder="t('sys.lock.placeholder')" v-model:value="password" />
-          <span :class="`${prefixCls}-entry__err-msg`" v-if="errMsgRef">
-            {{ t('sys.lock.alert') }}
-          </span>
-          <div :class="`${prefixCls}-entry__footer`">
-            <a-button
-              type="link"
-              size="small"
-              class="mt-2 mr-2"
-              :disabled="loadingRef"
-              @click="handleShowForm(true)"
-            >
-              {{ t('sys.lock.back') }}
-            </a-button>
-            <a-button
-              type="link"
-              size="small"
-              class="mt-2 mr-2"
-              :disabled="loadingRef"
-              @click="goLogin"
-            >
-              {{ t('sys.lock.backToLogin') }}
-            </a-button>
-            <a-button class="mt-2" type="link" size="small" @click="unLock()" :loading="loadingRef">
-              {{ t('sys.lock.entry') }}
-            </a-button>
-          </div>
-        </div>
-      </div>
-    </transition>
-
-    <div :class="`${prefixCls}__footer-date`">
-      <div class="time" v-show="!showDate">
-        {{ hour }}:{{ minute }} <span class="meridiem">{{ meridiem }}</span>
-      </div>
-      <div class="date"> {{ year }}/{{ month }}/{{ day }} {{ week }} </div>
-    </div>
-  </div>
+  <transition name="fade-bottom" mode="out-in">
+    <LockPage v-if="getIsLock" />
+  </transition>
 </template>
 <script lang="ts">
-  import { defineComponent, ref, computed } from 'vue';
-  import { Alert, Input } from 'ant-design-vue';
-
-  import { userStore } from '/@/store/modules/user';
-  import { lockStore } from '/@/store/modules/lock';
-  import { useI18n } from '/@/hooks/web/useI18n';
-
-  import { useNow } from './useNow';
-  import { useDesign } from '/@/hooks/web/useDesign';
-
-  import { LockOutlined } from '@ant-design/icons-vue';
-
+  import { defineComponent } from 'vue';
+  import LockPage from './LockPage.vue';
+  import { getIsLock } from '/@/hooks/web/useLockPage';
   export default defineComponent({
-    name: 'LockPage',
-    components: { Alert, LockOutlined, InputPassword: Input.Password },
-
+    name: 'Lock',
+    components: { LockPage },
     setup() {
-      const passwordRef = ref('');
-      const loadingRef = ref(false);
-      const errMsgRef = ref(false);
-      const showDate = ref(true);
-
-      const { prefixCls } = useDesign('lock-page');
-
-      const { start, stop, ...state } = useNow(true);
-
-      const { t } = useI18n();
-
-      const realName = computed(() => {
-        const { realName } = userStore.getUserInfoState || {};
-        return realName;
-      });
-
-      /**
-       * @description: unLock
-       */
-      async function unLock() {
-        if (!passwordRef.value) {
-          return;
-        }
-        let password = passwordRef.value;
-        try {
-          loadingRef.value = true;
-          const res = await lockStore.unLockAction({ password });
-          errMsgRef.value = !res;
-        } finally {
-          loadingRef.value = false;
-        }
-      }
-
-      function goLogin() {
-        userStore.loginOut(true);
-        lockStore.resetLockInfo();
-      }
-
-      function handleShowForm(show = false) {
-        showDate.value = show;
-      }
-
-      return {
-        goLogin,
-        realName,
-        unLock,
-        errMsgRef,
-        loadingRef,
-        t,
-        prefixCls,
-        showDate,
-        password: passwordRef,
-        handleShowForm,
-        ...state,
-      };
+      return { getIsLock };
     },
   });
 </script>
-<style lang="less" scoped>
-  @import (reference) '../../../design/index.less';
-  @prefix-cls: ~'@{namespace}-lock-page';
-
-  .@{prefix-cls} {
-    position: fixed;
-    top: 0;
-    right: 0;
-    bottom: 0;
-    left: 0;
-    z-index: 3000;
-    display: flex;
-    width: 100vw;
-    height: 100vh;
-    // background: rgba(23, 27, 41);
-    background: #000;
-    align-items: center;
-    justify-content: center;
-
-    &__unlock {
-      position: absolute;
-      top: 0;
-      left: 50%;
-      display: flex;
-      height: 50px;
-      padding-top: 20px;
-      font-size: 18px;
-      color: #fff;
-      cursor: pointer;
-      transform: translate(-50%, 0);
-      flex-direction: column;
-      align-items: center;
-      justify-content: space-between;
-      transition: all 0.3s;
-    }
-
-    &__date {
-      display: flex;
-      width: 100vw;
-      height: 100vh;
-      align-items: center;
-      justify-content: center;
-    }
-
-    &__hour {
-      position: relative;
-      margin-right: 80px;
-
-      .meridiem {
-        position: absolute;
-        top: 20px;
-        left: 20px;
-        font-size: 26px;
-      }
-      @media (max-width: @screen-xs) {
-        margin-right: 20px;
-      }
-    }
-
-    &__hour,
-    &__minute {
-      display: flex;
-      width: 40%;
-      height: 74%;
-      // font-size: 50em;
-      font-weight: 700;
-      color: #bababa;
-      background: #141313;
-      border-radius: 30px;
-      justify-content: center;
-      align-items: center;
-      // .respond-to(large-only, { font-size: 25em;});
-      // .respond-to(large-only, { font-size: 30em;});
-      @media (min-width: @screen-xxxl-min) {
-        font-size: 46em;
-      }
-      @media (min-width: @screen-xl-max) and (max-width: @screen-xxl-max) {
-        font-size: 38em;
-      }
-
-      @media (min-width: @screen-lg-max) and (max-width: @screen-xl-max) {
-        font-size: 30em;
-      }
-      @media (min-width: @screen-md-max) and (max-width: @screen-lg-max) {
-        font-size: 23em;
-      }
-      @media (min-width: @screen-sm-max) and (max-width: @screen-md-max) {
-        font-size: 19em;
-      }
-      @media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) {
-        font-size: 13em;
-      }
-      @media (max-width: @screen-xs) {
-        height: 50%;
-        font-size: 6em;
-        border-radius: 20px;
-      }
-    }
-
-    &__footer-date {
-      position: absolute;
-      bottom: 20px;
-      left: 50%;
-      font-family: helvetica;
-      color: #bababa;
-      transform: translate(-50%, 0);
-
-      .time {
-        font-size: 50px;
-
-        .meridiem {
-          font-size: 32px;
-        }
-      }
-
-      .date {
-        font-size: 26px;
-      }
-    }
-
-    &-entry {
-      position: absolute;
-      top: 0;
-      left: 0;
-      display: flex;
-      width: 100%;
-      height: 100%;
-      background: rgba(0, 0, 0, 0.5);
-      backdrop-filter: blur(10px);
-      justify-content: center;
-      align-items: center;
-
-      &-content {
-        width: 260px;
-      }
-
-      &__header {
-        text-align: center;
-
-        &-img {
-          width: 70px;
-          border-radius: 50%;
-        }
-
-        &-name {
-          margin-top: 5px;
-          font-weight: 500;
-          color: #bababa;
-        }
-      }
-
-      &__err-msg {
-        display: inline-block;
-        margin-top: 10px;
-        color: @error-color;
-      }
-
-      &__footer {
-        display: flex;
-        justify-content: space-between;
-      }
-    }
-  }
-</style>

File diff ditekan karena terlalu besar
+ 211 - 404
yarn.lock


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini