d3cbe1ba37
原本头部搜索框硬编码跳 /article,只能搜到文章——案例与标书虽然后端
早已支持 keywords 检索,前端却从未触达,这是「搜不到案例和标书」的根因。
本次新增 /search 统一结果页,三类一次搜全:
- server/api/search.get.ts:新增聚合接口,并发查三个上游并归一化输出
{article, case, tender}。用 Promise.allSettled 做部分降级——某类挂了只
降级该类,返回 {error:true}(不是 {list:[]},否则前端会把接口故障渲染成
「暂无结果」,等于对用户说谎),其余两类照常,整体不 500。
沿用既有代理的 status/deleted 过滤(文章/案例 status:0+deleted:0、
标书 status:1),这些是内容安全边界,防止草稿/下架/软删内容泄露到 C 端。
不改动既有 article/case/tender 三个 list 代理,它们有大量现存消费方。
- app/pages/search.vue + app/components/SearchResult.vue:公共页,一套 UI
服务全部 10 套模板(不逐模板实现——10 套里只有 template-07 有标书 UI)。
Tab 切换(文章→案例→标书),默认停第一个非空类;标书卡片展示价格与
截止时间(购买决策的关键信息);带关键词时 noindex,follow,沿用既有约定。
- app/components/SiteSearchBox.vue:跳转改为 /search。该组件被 10 套模板的
12 处 Header 共用,改这一处即全部生效。/article?keywords= 仍作为
「只搜文章」的域内检索入口保留,只是不再由头部搜索框触达。
实测:关键词「消防」下文章 7 条、案例 2 条正常渲染,计数 7/2/0 与总数 9
均正确;标书用「测试」命中 2 条,价格/截止时间/编号/招标人字段齐全。
部分降级与故障态(计数显示 —、文案与「暂无」区分开)已用桩数据逐一验证。
修复实测中发现的一个缺陷:某类接口故障时整页会误报「没有找到与…相关的
内容」——把接口故障谎报成「确实没有」。原因是整页空态判定 allEmpty 在
「某类为 null(故障)+ 其余为空」时恰好成立,已改为 allEmpty && !hasFailure。
77 lines
2.8 KiB
Vue
77 lines
2.8 KiB
Vue
<template>
|
|
<form
|
|
class="relative"
|
|
:style="accent ? { '--sb-accent': accent } : undefined"
|
|
@submit.prevent="onSearch"
|
|
>
|
|
<input
|
|
v-model="keyword"
|
|
type="text"
|
|
:placeholder="placeholder"
|
|
class="w-56 pl-4 pr-10 py-2 text-sm rounded-full border bg-[#F4F6FA] focus:outline-none focus:bg-white transition-colors"
|
|
:class="inputClass"
|
|
>
|
|
<button
|
|
type="submit"
|
|
class="absolute right-2 top-1/2 -translate-y-1/2 transition-colors"
|
|
:class="iconClass"
|
|
aria-label="搜索"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-4.35-4.35M17 11a6 6 0 11-12 0 6 6 0 0112 0z" />
|
|
</svg>
|
|
</button>
|
|
</form>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
/**
|
|
* 共享头部搜索框(Nuxt 自动导入,全局可用)
|
|
* 由各模板 Header 在 logo 右侧以 <SiteSearchBox v-if="showHeaderSearch" /> 挂载。
|
|
* 是否显示由 useSite().showHeaderSearch(后台 setting.searchBtn)控制。
|
|
* 提交后带关键词跳转 /search?keywords=xxx(统一搜索结果页,覆盖文章/案例/标书三类)。
|
|
*
|
|
* 定位(ml-8 / ml-auto 等)由父组件通过 class 透传(fallthrough 合并到根 <form>);
|
|
* 视觉主题通过 variant / accent 适配各模板配色。
|
|
*/
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
/** 头部背景:light=浅色头部(默认),dark=深色头部 */
|
|
variant?: 'light' | 'dark'
|
|
/** 主题色(聚焦边框 / 图标 hover 色),支持 CSS 变量名如 'var(--t2-primary)' */
|
|
accent?: string
|
|
placeholder?: string
|
|
}>(),
|
|
{
|
|
variant: 'light',
|
|
placeholder: '请输入关键词...'
|
|
}
|
|
)
|
|
|
|
const router = useRouter()
|
|
const keyword = ref('')
|
|
|
|
const inputClass = computed(() => {
|
|
if (props.variant === 'dark') {
|
|
return 'border-[#333] bg-[#222] text-gray-100 placeholder-gray-500 focus:border-[color:var(--sb-accent,#d4af37)]'
|
|
}
|
|
return 'border-[#E5E7EB] bg-[#F4F6FA] text-gray-800 placeholder-gray-400 focus:border-[color:var(--sb-accent,#1E2A47)]'
|
|
})
|
|
|
|
const iconClass = computed(() => {
|
|
if (props.variant === 'dark') {
|
|
return 'text-gray-300 hover:text-[color:var(--sb-accent,#d4af37)]'
|
|
}
|
|
return 'text-gray-500 hover:text-[color:var(--sb-accent,#1E2A47)]'
|
|
})
|
|
|
|
function onSearch() {
|
|
const kw = keyword.value.trim()
|
|
// 跳到统一搜索结果页,一次覆盖文章 / 案例 / 标书三类。
|
|
// 历史行为是跳 /article(只会搜到文章),即「搜不到案例和标书」的根因,
|
|
// 见 .scratch/web-unified-search/issues/01-search-page-url-and-routing.md。
|
|
// /article?keywords= 仍作为「只搜文章」的域内检索入口保留,只是不再由本搜索框触达。
|
|
router.push({ path: '/search', query: kw ? { keywords: kw } : {} })
|
|
}
|
|
</script>
|