Skip to content

Commit

Permalink
wip: 🕔 菜单搜索功能开发中
Browse files Browse the repository at this point in the history
  • Loading branch information
ita-code committed Jan 31, 2024
1 parent bc26786 commit 361b8f2
Show file tree
Hide file tree
Showing 13 changed files with 401 additions and 117 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,5 @@ YiLe-Admin

- [ ] 菜单搜索功能
- [ ] 登录页设计修改
- [ ] 组件自动引入和Vue导入,引发问题类型报错
- [-] 组件自动引入和Vue导入,引发问题类型报错
- [ ] icon解决方案
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"vue": "^3.4.3",
"vue-i18n": "^9.8.0",
"vue-router": "^4.2.5",
"vuedraggable": "^4.1.0"
"vuedraggable": "^4.1.0",
"hotkeys-js": "^3.13.5"
},
"devDependencies": {
"@commitlint/cli": "^18.4.3",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Kbd/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<kbd
class="mr-[4px] h-6 min-w-[24px] inline-flex items-center justify-center rounded bg-stone-1 px-1 text-[12px] text-dark font-medium font-sans ring-1 ring-stone-3 ring-inset last:mr-0 dark:bg-dark-9 dark:text-white dark:ring-stone-7"
class="mr1 h-6 min-w-[24px] inline-flex items-center justify-center rounded bg-stone-1 px-1 text-[12px] text-dark font-medium font-sans ring-1 ring-stone-3 ring-inset last:mr-0 dark:bg-dark-9 dark:text-white dark:ring-stone-7"
>
<slot />
</kbd>
Expand Down
26 changes: 26 additions & 0 deletions src/hooks/useBoolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ref } from "vue";

export function useBoolean(initValue = false) {
const bool = ref(initValue);

function setBool(value: boolean) {
bool.value = value;
}
function setTrue() {
setBool(true);
}
function setFalse() {
setBool(false);
}
function toggle() {
setBool(!bool.value);
}

return {
bool,
setBool,
setTrue,
setFalse,
toggle
};
}
2 changes: 1 addition & 1 deletion src/layouts/components/Header/ToolBarRight.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { useUserStore } from "@/stores/modules/user";
import AssemblySize from "./components/AssemblySize.vue";
import Language from "./components/Language.vue";
import SearchMenu from "./components/SearchMenu.vue";
import SearchMenu from "./components/SearchMenu/index.vue";
import ThemeSetting from "./components/ThemeSetting.vue";
import Message from "./components/Message.vue";
import Fullscreen from "./components/Fullscreen.vue";
Expand Down
113 changes: 0 additions & 113 deletions src/layouts/components/Header/components/SearchMenu.vue

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script setup lang="ts">
const props = withDefaults(defineProps<{ total: number }>(), {
total: 0
});
</script>

<template>
<div class="text-3.5 flex text-[#333] dark:text-white">
<span class="flex-center mr3">
<Kbd> O </Kbd>
确认
</span>
<span class="flex-center mr3">
<Kbd>
<el-icon><CaretTop /></el-icon>
</Kbd>
<Kbd>
<el-icon><CaretBottom /></el-icon>
</Kbd>
切换
</span>
<span class="flex-center mr3">
<Kbd> ESC </Kbd>
关闭
</span>
<p v-if="props.total > 0" class="absolute right-5 m0">共{{ props.total }}项</p>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import SearchResult from "./SearchResult.vue";
import SearchFooter from "./SearchFooter.vue";
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
import { useAuthStore } from "@/stores/modules/auth";
interface Props {
/** 弹窗显隐 */
value: boolean;
}
interface Emits {
(e: "update:value", val: boolean): void;
}
//手机端
const emit = defineEmits<Emits>();
const props = withDefaults(defineProps<Props>(), {});
const router = useRouter();
// const { locale } = useI18n();
const keyword = ref("");
const scrollbarRef = ref();
const resultRef = ref();
const activePath = ref("");
const inputRef = ref<HTMLInputElement | null>(null);
const resultOptions = shallowRef([]);
const handleSearch = useDebounceFn(search, 300);
/** 菜单树形结构 */
const authStore = useAuthStore();
const menusData = computed(() => authStore.flatMenuListGet.filter(item => !item.meta.isHide));
const show = computed({
get() {
return props.value;
},
set(val: boolean) {
emit("update:value", val);
}
});
/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */
function flatTree(arr) {
const res = [];
function deep(arr) {
arr.forEach(item => {
res.push(item);
item.children && deep(item.children);
});
}
deep(arr);
return res;
}
/** 查询 */
function search() {
const flatMenusData = flatTree(menusData.value);
resultOptions.value = flatMenusData.filter(menu =>
keyword.value
? menu.meta?.title.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim())
: // ||
// (locale.value === "zh" &&
// !isAllEmpty(match(menu.meta?.title.toLocaleLowerCase(), keyword.value.toLocaleLowerCase().trim())))
false
);
if (resultOptions.value?.length > 0) {
activePath.value = resultOptions.value[0].path;
} else {
activePath.value = "";
}
}
function handleClose() {
show.value = false;
/** 延时处理防止用户看到某些操作 */
setTimeout(() => {
resultOptions.value = [];
keyword.value = "";
}, 200);
}
function scrollTo(index) {
const scrollTop = resultRef.value.handleScroll(index);
scrollbarRef.value.setScrollTop(scrollTop);
}
/** key up */
function handleUp() {
const { length } = resultOptions.value;
if (length === 0) return;
const index = resultOptions.value.findIndex(item => item.path === activePath.value);
if (index === 0) {
activePath.value = resultOptions.value[length - 1].path;
scrollTo(resultOptions.value.length - 1);
} else {
activePath.value = resultOptions.value[index - 1].path;
scrollTo(index - 1);
}
}
/** key down */
function handleDown() {
const { length } = resultOptions.value;
if (length === 0) return;
const index = resultOptions.value.findIndex(item => item.path === activePath.value);
if (index + 1 === length) {
activePath.value = resultOptions.value[0].path;
} else {
activePath.value = resultOptions.value[index + 1].path;
}
scrollTo(index + 1);
}
/** key enter */
function handleEnter() {
const { length } = resultOptions.value;
if (length === 0 || activePath.value === "") return;
router.push(activePath.value);
handleClose();
}
onKeyStroke("Enter", handleEnter);
onKeyStroke("ArrowUp", handleUp);
onKeyStroke("ArrowDown", handleDown);
</script>

<template>
<el-dialog
v-model="show"
top="5vh"
:show-close="false"
:width="'40vw'"
:before-close="handleClose"
:style="{
borderRadius: '6px'
}"
@opened="inputRef!.focus()"
@closed="inputRef!.blur()"
class="search-dialog"
>
<el-input ref="inputRef" v-model="keyword" size="large" clearable placeholder="搜索菜单" @input="handleSearch">
<template #prefix>
<el-icon class="text-primary w-[24px] h-[24px]"><Search /></el-icon>
</template>
</el-input>
<div class="search-result-container">
<el-scrollbar ref="scrollbarRef" max-height="calc(90vh - 140px)">
<el-empty v-if="resultOptions.length === 0" description="暂无搜索结果" />
<SearchResult v-else ref="resultRef" v-model:value="activePath" :options="resultOptions" @click="handleEnter" />
</el-scrollbar>
</div>
<template #footer>
<SearchFooter :total="resultOptions.length" />
</template>
</el-dialog>
</template>

<style lang="scss" scoped></style>
Loading

0 comments on commit 361b8f2

Please sign in to comment.