1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-07-27 04:20:43 +08:00

feat: 新增全局搜索功能,优化页面默认跳转逻辑

1. 新增全局搜索弹窗组件及相关事件常量
2. 修复首页默认跳转路径,改回首页而非动态页
3. 注释无用的单例运行代码
4. 添加背景模糊样式类
5. 更新搜索API类型定义
6. 底部导航栏搜索按钮触发全局搜索弹窗
This commit is contained in:
小莫唐尼
2026-06-13 00:29:49 +08:00
parent 8510b71ff6
commit 50cad268d3
9 changed files with 218 additions and 6 deletions
+8
View File
@@ -0,0 +1,8 @@
---
alwaysApply: true
scene: git_message
---
在此处编写规则,自定义 AI 生成提交信息的风格。
使用中文编写提交信息
+15
View File
@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { GLOBAL_SEARCH_EVENT_NAME } from '@/constants/events'
import FgTabbar from '@/tabbar/index.vue' import FgTabbar from '@/tabbar/index.vue'
import { isPageTabbar } from './tabbar/store' import { isPageTabbar } from './tabbar/store'
import { currRoute } from './utils' import { currRoute } from './utils'
@@ -21,6 +22,17 @@ const helloKuRoot = ref('Hello AppKuVue')
const exposeRef = ref('this is form app.Ku.vue') const exposeRef = ref('this is form app.Ku.vue')
const isShowGlobalSearch = ref(false)
function onShowGlobalSearch() {
isShowGlobalSearch.value = !isShowGlobalSearch.value
console.log('isShowGlobalSearch', isShowGlobalSearch.value)
}
onMounted(() => {
uni.$off(GLOBAL_SEARCH_EVENT_NAME, onShowGlobalSearch)
uni.$on(GLOBAL_SEARCH_EVENT_NAME, onShowGlobalSearch)
})
defineExpose({ defineExpose({
exposeRef, exposeRef,
}) })
@@ -35,6 +47,9 @@ defineExpose({
<KuRootView /> <KuRootView />
<!-- 全局搜索弹窗 -->
<uh-global-search v-if="isShowGlobalSearch" />
<FgTabbar v-if="isCurrentPageTabbar" /> <FgTabbar v-if="isCurrentPageTabbar" />
</view> </view>
</template> </template>
+2 -2
View File
@@ -1,5 +1,5 @@
import { http } from '@/http/alova' import { http } from '@/http/alova'
import type { IndexV1alpha1PublicApiIndicesSearchRequest, SearchResult } from '@halo-dev/api-client' import type { SearchOption, SearchResult } from '@halo-dev/api-client'
/** /**
* 检查插件是否可用 * 检查插件是否可用
@@ -17,7 +17,7 @@ export function checkPluginAvailable(pluginId: string) {
* @param data 搜索参数 * @param data 搜索参数
* @returns 搜索结果 * @returns 搜索结果
*/ */
export function indicesSearch(data: IndexV1alpha1PublicApiIndicesSearchRequest) { export function indicesSearch(data: SearchOption) {
return http.Post<SearchResult>(`/apis/api.halo.run/v1alpha1/indices/-/search`, data, { return http.Post<SearchResult>(`/apis/api.halo.run/v1alpha1/indices/-/search`, data, {
meta: { isHalo: true }, meta: { isHalo: true },
}) })
@@ -0,0 +1,166 @@
<script setup lang="ts">
import { indicesSearch } from '@/api/halo-base/common'
import { timeFormat } from '@/utils/base/timeFormat'
import { debounce } from '@/utils/base/debounce'
import { GLOBAL_SEARCH_EVENT_NAME } from '@/constants/events'
import type { HaloDocument, SearchOption, SearchResult } from '@halo-dev/api-client'
const { loading, error, data, run } = useRequest<SearchResult, SearchOption>(indicesSearch, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
const scroll = reactive({
top: 0,
})
const searchParams = ref<SearchOption>({
keyword: undefined,
limit: 50,
})
const searchResultTypeMap = {
'moment.moment.halo.run': 'moment',
'post.content.halo.run': 'post',
'doc.halo.run': 'doc',
}
const searchResultTypeCN = {
'moment.moment.halo.run': '瞬间',
'post.content.halo.run': '文章',
'doc.halo.run': '文档',
}
interface SearchResultItem extends HaloDocument {
releaseTime: string
typeCN: string
}
const searchResult = computed<Array<SearchResultItem>>(() => {
if (!data.value || data.value?.hits?.length === 0) {
return []
}
return data.value.hits.map((item) => {
return {
...item,
type: searchResultTypeMap[item.type],
typeCN: searchResultTypeCN[item.type],
releaseTime: timeFormat(item.updateTimestamp ?? item.creationTimestamp),
}
})
})
function handleSearch() {
if (!searchParams.value.keyword) {
uni.showToast({
title: '请输入搜索内容',
icon: 'none',
})
return
}
scroll.top = 0
run(searchParams.value)
}
function handleClose() {
setTimeout(() => {
uni.$emit(GLOBAL_SEARCH_EVENT_NAME)
}, 30)
}
const onViewScroll = debounce((e: any) => {
scroll.top = e.detail.scrollTop
}, 150)
onMounted(() => {
handleSearch()
})
</script>
<template>
<view
class="uh-backdrop-blur fixed inset-0 z-110 flex items-center justify-center bg-black/20"
@click.stop="handleClose"
>
<!-- 内容区 -->
<view class="box-border h-[65vh] w-[80vw] flex flex-col overflow-hidden rounded-2xl bg-page2 p-2" @click.stop>
<view class="box-border w-full flex shrink-0 items-center justify-between gap-x-4 p-2">
<view class="flex flex-1 items-center gap-x-2">
<input
v-model="searchParams.keyword" :auto-focus="true" type="text" placeholder="请输入搜索内容"
class="flex-1 border-2 border-gray-900 rounded-md border-solid px-2 py-1 text-xs text-gray-900 placeholder:text-xs"
>
<view
class="flex items-center justify-center rounded-lg bg-gray-900 px-3 py-2"
@click="handleSearch()"
>
<wd-icon name="search-line" color="#ffffff" :size="16" />
<text class="text-xs text-white">搜索</text>
</view>
</view>
<view class="shrink-0">
<wd-icon name="close-circle" size="24px" color="#1A1A1A" @click="handleClose" />
</view>
</view>
<!-- 搜索结果区 -->
<view class="mb-2 box-border flex items-center justify-between px-2">
<text class="text-xs text-gray-900 font-bold">搜索结果</text>
<text class="text-xs text-gray-600"> {{ searchResult.length }} </text>
</view>
<scroll-view
v-if="searchResult.length !== 0" class="h-[53vh] w-full" :scroll-y="true"
:scroll-top="scroll.top" @scroll="onViewScroll"
>
<view class="box-border flex flex-col gap-y-3 px-2">
<!-- 卡片 -->
<view
v-for="(item, index) in searchResult" :key="item.metadataName"
class="uh-shadow-xs box-border w-full flex flex-col gap-y-2 rounded-lg bg-white p-3"
>
<view class="flex items-center gap-x-2">
<view
class="flex items-center rounded-md bg-gray-900 px-1.5 py-0.5 text-xs text-white font-bold"
>
# <text class="pl-0.5 text-[12px]">{{ index + 1 }}</text>
</view>
<rich-text :nodes="item.title" class="line-clamp-1 text-sm text-gray-900 font-bold" />
</view>
<view class="line-clamp-2 w-full text-xs text-gray-600 leading-relaxed">
<rich-text :nodes="item.description ?? item.content" />
</view>
<view class="h-1px w-full bg-gray-50" />
<view class="flex items-center justify-between gap-x-4">
<view class="rounded-md bg-gray-900 px-2 py-1 text-[11px] text-white">
来自{{ item.typeCN }}
</view>
<text class="text-xs text-gray-600">{{ item.releaseTime }}</text>
</view>
</view>
</view>
</scroll-view>
<view v-else-if="loading" class="flex flex-1 items-center justify-center">
<wd-loading text="搜索中..." color="#1A1A1A" :size="52" />
</view>
<view v-else-if="error" class="flex flex-1 items-center justify-center">
<wd-empty tip="搜索失败">
<template #image>
<wd-icon name="close-circle" color="#1A1A1A" :size="52" />
</template>
<template #bottom>
<view
class="flex items-center justify-center rounded-lg bg-gray-900 px-3 py-2"
@click="handleSearch()"
>
<wd-icon name="search-line" color="#ffffff" :size="16" />
<text class="pl-1 text-xs text-white">点击重试</text>
</view>
</template>
</wd-empty>
</view>
<view v-else class="flex flex-1 items-center justify-center">
<wd-empty tip="暂无搜索结果">
<template #image>
<wd-icon name="empty" color="#1A1A1A" :size="52" />
</template>
</wd-empty>
</view>
</view>
</view>
</template>
+2
View File
@@ -0,0 +1,2 @@
/** 全局搜索弹窗时间名称 */
export const GLOBAL_SEARCH_EVENT_NAME = 'uh-show-global-search'
+1 -1
View File
@@ -115,7 +115,7 @@ const { list: postList, refresh } = useListData<ListedPostVo>({
onLoad(() => { onLoad(() => {
console.log('home onLoad') console.log('home onLoad')
refresh() refresh()
runSingle({ name: '019eb1ca-e37c-7729-97af-e0b658aeef0f' }) // runSingle({ name: '019eb1ca-e37c-7729-97af-e0b658aeef0f' })
}) })
onPageScroll(() => { }) onPageScroll(() => { })
+2 -2
View File
@@ -14,8 +14,8 @@ definePage({
onLoad(() => { onLoad(() => {
console.log('index onLoad') console.log('index onLoad')
// 默认跳转首页 // 默认跳转首页
// uni.switchTab({ url: '/pages/home/home' }) uni.switchTab({ url: '/pages/home/home' })
uni.switchTab({ url: '/pages/moments/moments' }) // uni.switchTab({ url: '/pages/moments/moments' })
}) })
</script> </script>
+16
View File
@@ -9,3 +9,19 @@
.uh-shadow-card { .uh-shadow-card {
box-shadow: 0 16rpx 60rpx rgba(0, 0, 0, 0.04); box-shadow: 0 16rpx 60rpx rgba(0, 0, 0, 0.04);
} }
.uh-backdrop-blur {
backdrop-filter: blur(4rpx);
}
.uh-backdrop-blur-xs {
backdrop-filter: blur(8rpx);
}
.uh-backdrop-blur-sm {
backdrop-filter: blur(12rpx);
}
.uh-backdrop-blur-md {
backdrop-filter: blur(24rpx);
}
+6 -1
View File
@@ -5,6 +5,7 @@ import { setTabbarItem } from './i18n'
import { tabbarList, tabbarStore } from './store' import { tabbarList, tabbarStore } from './store'
import { sleep } from '@/utils/common' import { sleep } from '@/utils/common'
import { debounce } from '@/utils/base/debounce' import { debounce } from '@/utils/base/debounce'
import { GLOBAL_SEARCH_EVENT_NAME } from '@/constants/events'
import TabbarItem from './TabbarItem.vue' import TabbarItem from './TabbarItem.vue'
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
@@ -132,6 +133,10 @@ function handleScrollToTop() {
duration: 300, duration: 300,
}) })
} }
function handleShowGlobalSearch() {
uni.$emit(GLOBAL_SEARCH_EVENT_NAME)
}
</script> </script>
<template> <template>
@@ -159,7 +164,7 @@ function handleScrollToTop() {
class="uh-shadow-sm uh-backdrop-blur box-border h-54px w-54px flex items-center justify-center border border-white rounded-full border-solid bg-white/95 text-2xl" class="uh-shadow-sm uh-backdrop-blur box-border h-54px w-54px flex items-center justify-center border border-white rounded-full border-solid bg-white/95 text-2xl"
> >
<wd-icon v-if="backTopActive.show" name="arrow-up" :size="22" @click="handleScrollToTop" /> <wd-icon v-if="backTopActive.show" name="arrow-up" :size="22" @click="handleScrollToTop" />
<wd-icon v-else name="search-line" :size="22" /> <wd-icon v-else name="search-line" :size="22" @click="handleShowGlobalSearch()" />
</view> </view>
<view v-if="!tabBarConfig.useFloat" class="pb-safe" /> <view v-if="!tabBarConfig.useFloat" class="pb-safe" />