refactor: 重构 SearchMenu 组件结构细节

This commit is contained in:
pany 2024-11-25 10:10:24 +08:00
parent ff4ae8ba5b
commit 30b4754493
2 changed files with 29 additions and 43 deletions

View File

@ -22,7 +22,7 @@ const scrollbarRef = ref<InstanceType<typeof ElScrollbar> | null>(null)
const resultRef = ref<InstanceType<typeof Result> | null>(null) const resultRef = ref<InstanceType<typeof Result> | null>(null)
const keyword = ref<string>("") const keyword = ref<string>("")
const resultList = shallowRef<RouteRecordRaw[]>([]) const result = shallowRef<RouteRecordRaw[]>([])
const activeRouteName = ref<RouteRecordName | undefined>(undefined) const activeRouteName = ref<RouteRecordName | undefined>(undefined)
/** 是否按下了上键或下键(用于解决和 mouseenter 事件的冲突) */ /** 是否按下了上键或下键(用于解决和 mouseenter 事件的冲突) */
const isPressUpOrDown = ref<boolean>(false) const isPressUpOrDown = ref<boolean>(false)
@ -30,17 +30,16 @@ const isPressUpOrDown = ref<boolean>(false)
/** 控制搜索对话框宽度 */ /** 控制搜索对话框宽度 */
const modalWidth = computed(() => (isMobile.value ? "80vw" : "40vw")) const modalWidth = computed(() => (isMobile.value ? "80vw" : "40vw"))
/** 树形菜单 */ /** 树形菜单 */
const menusData = computed(() => cloneDeep(usePermissionStore().routes)) const menus = computed(() => cloneDeep(usePermissionStore().routes))
/** 搜索(防抖) */ /** 搜索(防抖) */
const handleSearch = debounce(() => { const handleSearch = debounce(() => {
const flatMenusData = flatTree(menusData.value) const flatMenus = flatTree(menus.value)
resultList.value = flatMenusData.filter(menu => const _keywords = keyword.value.toLocaleLowerCase().trim()
keyword.value ? menu.meta?.title?.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim()) : false result.value = flatMenus.filter(menu => keyword.value ? menu.meta?.title?.toLocaleLowerCase().includes(_keywords) : false)
)
// //
const length = resultList.value?.length const length = result.value?.length
activeRouteName.value = length > 0 ? resultList.value[0].name : undefined activeRouteName.value = length > 0 ? result.value[0].name : undefined
}, 500) }, 500)
/** 将树形菜单扁平化为一维数组,用于菜单搜索 */ /** 将树形菜单扁平化为一维数组,用于菜单搜索 */
@ -58,7 +57,7 @@ function handleClose() {
// //
setTimeout(() => { setTimeout(() => {
keyword.value = "" keyword.value = ""
resultList.value = [] result.value = []
}, 200) }, 200)
} }
@ -73,16 +72,16 @@ function scrollTo(index: number) {
/** 键盘上键 */ /** 键盘上键 */
function handleUp() { function handleUp() {
isPressUpOrDown.value = true isPressUpOrDown.value = true
const { length } = resultList.value const { length } = result.value
if (length === 0) return if (length === 0) return
// name // name
const index = resultList.value.findIndex(item => item.name === activeRouteName.value) const index = result.value.findIndex(item => item.name === activeRouteName.value)
// //
if (index === 0) { if (index === 0) {
const bottomName = resultList.value[length - 1].name const bottomName = result.value[length - 1].name
// bottomName 1 name // bottomName 1 name
if (activeRouteName.value === bottomName && length > 1) { if (activeRouteName.value === bottomName && length > 1) {
activeRouteName.value = resultList.value[length - 2].name activeRouteName.value = result.value[length - 2].name
scrollTo(length - 2) scrollTo(length - 2)
} else { } else {
// //
@ -90,7 +89,7 @@ function handleUp() {
scrollTo(length - 1) scrollTo(length - 1)
} }
} else { } else {
activeRouteName.value = resultList.value[index - 1].name activeRouteName.value = result.value[index - 1].name
scrollTo(index - 1) scrollTo(index - 1)
} }
} }
@ -98,16 +97,16 @@ function handleUp() {
/** 键盘下键 */ /** 键盘下键 */
function handleDown() { function handleDown() {
isPressUpOrDown.value = true isPressUpOrDown.value = true
const { length } = resultList.value const { length } = result.value
if (length === 0) return if (length === 0) return
// name name // name name
const index = resultList.value.map(item => item.name).lastIndexOf(activeRouteName.value) const index = result.value.map(item => item.name).lastIndexOf(activeRouteName.value)
// //
if (index === length - 1) { if (index === length - 1) {
const topName = resultList.value[0].name const topName = result.value[0].name
// topName 1 name // topName 1 name
if (activeRouteName.value === topName && length > 1) { if (activeRouteName.value === topName && length > 1) {
activeRouteName.value = resultList.value[1].name activeRouteName.value = result.value[1].name
scrollTo(1) scrollTo(1)
} else { } else {
// //
@ -115,30 +114,23 @@ function handleDown() {
scrollTo(0) scrollTo(0)
} }
} else { } else {
activeRouteName.value = resultList.value[index + 1].name activeRouteName.value = result.value[index + 1].name
scrollTo(index + 1) scrollTo(index + 1)
} }
} }
/** 键盘回车键 */ /** 键盘回车键 */
function handleEnter() { function handleEnter() {
const { length } = resultList.value const { length } = result.value
if (length === 0) return if (length === 0) return
const name = activeRouteName.value const name = activeRouteName.value
const path = resultList.value.find(item => item.name === name)?.path const path = result.value.find(item => item.name === name)?.path
if (path && isExternal(path)) { if (path && isExternal(path)) return window.open(path, "_blank", "noopener, noreferrer")
window.open(path, "_blank", "noopener, noreferrer") if (!name) return ElMessage.warning("无法通过搜索进入该菜单,请为对应的路由设置唯一的 Name")
return
}
if (!name) {
ElMessage.warning("无法通过搜索进入该菜单,请为对应的路由设置唯一的 Name")
return
}
try { try {
router.push({ name }) router.push({ name })
} catch { } catch {
ElMessage.error("该菜单有必填的动态参数,无法通过搜索进入") return ElMessage.error("该菜单有必填的动态参数,无法通过搜索进入")
return
} }
handleClose() handleClose()
} }
@ -169,21 +161,21 @@ function handleReleaseUpOrDown() {
<SvgIcon name="search" /> <SvgIcon name="search" />
</template> </template>
</el-input> </el-input>
<el-empty v-if="resultList.length === 0" description="暂无搜索结果" :image-size="100" /> <el-empty v-if="result.length === 0" description="暂无搜索结果" :image-size="100" />
<template v-else> <template v-else>
<p>搜索结果</p> <p>搜索结果</p>
<el-scrollbar ref="scrollbarRef" max-height="40vh" always> <el-scrollbar ref="scrollbarRef" max-height="40vh" always>
<Result <Result
ref="resultRef" ref="resultRef"
v-model="activeRouteName" v-model="activeRouteName"
:data="resultList" :data="result"
:is-press-up-or-down="isPressUpOrDown" :is-press-up-or-down="isPressUpOrDown"
@click="handleEnter" @click="handleEnter"
/> />
</el-scrollbar> </el-scrollbar>
</template> </template>
<template #footer> <template #footer>
<Footer :total="resultList.length" /> <Footer :total="result.length" />
</template> </template>
</el-dialog> </el-dialog>
</template> </template>

View File

@ -47,19 +47,13 @@ function getScrollTop(index: number) {
} }
// //
onBeforeMount(() => { onBeforeMount(() => window.addEventListener("resize", getScrollbarHeight))
window.addEventListener("resize", getScrollbarHeight)
})
// //
onMounted(() => { onMounted(() => getScrollbarHeight())
getScrollbarHeight()
})
// //
onBeforeUnmount(() => { onBeforeUnmount(() => window.removeEventListener("resize", getScrollbarHeight))
window.removeEventListener("resize", getScrollbarHeight)
})
defineExpose({ getScrollTop }) defineExpose({ getScrollTop })
</script> </script>