2023-06-20 14:10:35 +08:00

230 lines
6.4 KiB
TypeScript

/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-04-16
*
* @workspace ray-template
*
* @remark 今天也是元气满满撸代码的一天
*/
import './index.scss'
import { NInput, NModal, NResult, NScrollbar, NSpace } from 'naive-ui'
import RayIcon from '@/components/RayIcon/index'
import { on, off } from '@/utils/element'
import { debounce } from 'lodash-es'
import { useMenu } from '@/store'
import { validMenuItemShow } from '@/router/helper/routerCopilot'
import type { MenuOption } from 'naive-ui'
import type { AppRouteMeta } from '@/router/type'
const GlobalSeach = defineComponent({
name: 'GlobalSeach',
props: {
show: {
type: Boolean,
default: false,
},
},
emits: ['update:show'],
setup(props, { emit }) {
const menuStore = useMenu()
const { menuModelValueChange } = menuStore
const modelShow = computed({
get: () => props.show,
set: (val) => {
emit('update:show', val)
if (!val) {
state.searchOptions = []
state.searchValue = null
}
},
})
const modelMenuOptions = computed(() => menuStore.options)
const state = reactive({
searchValue: null,
searchOptions: [] as IMenuOptions[],
})
const tiptextOptions = [
{
icon: 'cmd / ctrl + k',
label: '唤起',
plain: true,
},
{
icon: 'esc',
label: '关闭',
plain: true,
},
]
/** 按下 ctrl + k 或者 command + k 激活搜索栏 */
const registerKeyboard = (e: Event) => {
const _e = e as KeyboardEvent
if ((_e.ctrlKey || _e.metaKey) && _e.key === 'k') {
modelShow.value = true
}
}
/** 根据输入值模糊检索菜单 */
const handleSearchMenuOptions = (value: string) => {
const arr: IMenuOptions[] = []
const filterArr = (options: IMenuOptions[]) => {
options.forEach((curr) => {
if (curr.children?.length) {
filterArr(curr.children)
}
/** 处理菜单名与输入值, 不区分大小写 */
const _breadcrumbLabel = curr.breadcrumbLabel?.toLocaleLowerCase()
const _value = String(value).toLocaleLowerCase()
if (
_breadcrumbLabel?.includes(_value) &&
validMenuItemShow(curr) &&
!curr.children?.length
) {
arr.push(curr)
}
})
}
if (value) {
filterArr(modelMenuOptions.value)
state.searchOptions = arr
} else {
state.searchOptions = []
}
}
const handleSearchItemClick = (option: MenuOption) => {
const meta = option.meta as AppRouteMeta
/** 如果配置站外跳转则不会关闭搜索框 */
if (meta.windowOpen) {
window.open(meta.windowOpen)
} else {
modelShow.value = false
menuModelValueChange(option.key as string, option)
}
}
/** 渲染搜索菜单前缀图标, 如果没有则用 icon table 代替 */
const RenderPreIcon = (meta: AppRouteMeta) => {
const { icon } = meta
if (typeof icon === 'string') {
return <RayIcon name={icon} size="24" />
} else if (typeof icon === 'function') {
return () => icon
} else {
return <RayIcon name="table" size="24" />
}
}
onMounted(() => {
on(window, 'keydown', registerKeyboard)
})
onBeforeUnmount(() => {
off(window, 'keydown', registerKeyboard)
})
return {
...toRefs(state),
modelShow,
tiptextOptions,
handleSearchMenuOptions: debounce(handleSearchMenuOptions, 300),
handleSearchItemClick,
RenderPreIcon,
}
},
render() {
return (
<NModal v-model:show={this.modelShow} transform-origin="center" show>
<div class="global-seach global-seach--dark global-seach--light">
<div class="global-seach__wrapper">
<div class="global-seach__card">
<div class="global-seach__card-header">
<NInput
size="large"
v-model:value={this.searchValue}
clearable
onInput={this.handleSearchMenuOptions.bind(this)}
>
{{
prefix: () => <RayIcon name="search" size="24" />,
}}
</NInput>
</div>
<NScrollbar class="global-seach__card-content">
{this.searchOptions.length ? (
<NSpace vertical wrapItem={false} size={[8, 8]}>
{this.searchOptions.map((curr) => (
<NSpace
align="center"
wrapItem={false}
class="content-item"
{...{
onClick: this.handleSearchItemClick.bind(this, curr),
}}
>
<div class="content-item-icon">
{this.RenderPreIcon(curr.meta)}
</div>
<div class="content-item-label">
{curr.breadcrumbLabel}
</div>
</NSpace>
))}
</NSpace>
) : (
<NResult size="large" description="暂无搜索结果">
{{
icon: () => '',
}}
</NResult>
)}
</NScrollbar>
<div class="global-seach__card-footer">
<NSpace
class="card-footer__tip-wrapper"
align="center"
wrapItem={false}
size={[24, 8]}
>
{this.tiptextOptions.map((curr) => (
<div class="tip-wrapper-item">
<div class="item-icon">
{curr.plain ? (
<span>{curr.icon}</span>
) : (
<RayIcon name={curr.icon} size="18" />
)}
</div>
<div class="item-laebl">{curr.label}</div>
</div>
))}
</NSpace>
</div>
</div>
</div>
</div>
</NModal>
)
},
})
export default GlobalSeach