refactor: 重构 SearchMenu 组件结构细节
This commit is contained in:
parent
ff4ae8ba5b
commit
30b4754493
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user