feat(buyer): 增强搜索功能并提升用户体验

- 新增 `getStoreById` 接口方法,用于获取包含评分在内的店铺信息
- `Search.vue` 新增搜索建议面板,展示搜索历史和热词,优化搜索体验
- 新增 `SearchSuggest.vue` 组件,用于展示搜索建议和搜索历史
- `App.vue` 根据路由元信息按条件设置页面标题
- 优化多个组件的布局与样式,让整体风格更统一
- 重构多个组件的跳转逻辑,改用路由跳转(`router.push`)替代新开窗口(`window.open`),提升用户体验
This commit is contained in:
pikachu1995@126.com
2026-10-02 15:26:58 +08:00
parent 4777eb2dd6
commit 8410179885
44 changed files with 2116 additions and 674 deletions

View File

@@ -0,0 +1,151 @@
<template>
<div>
<div v-if="keyword" class="suggest-section">
<div v-if="suggesting && !suggestions.length" class="suggest-empty">正在查找...</div>
<button
v-for="item in suggestions"
:key="item"
type="button"
class="suggest-item"
@click="$emit('pick', item)"
>
<span v-for="(part, index) in parts(item)" :key="index" :class="{ hit: part.hit }">{{ part.text }}</span>
</button>
<div v-if="!suggesting && !suggestions.length" class="suggest-empty">没有匹配的商品,回车查看全部结果</div>
</div>
<template v-else>
<div v-if="history.length" class="suggest-section">
<div class="suggest-head">
<span>搜索历史</span>
<button type="button" class="suggest-clear" @click="$emit('clear')">清空</button>
</div>
<div class="suggest-tags">
<button
v-for="item in history"
:key="item"
type="button"
class="suggest-tag"
@click="$emit('pick', item)"
>{{ item }}</button>
</div>
</div>
<div v-if="hotWords.length" class="suggest-section">
<div class="suggest-head"><span>热门搜索</span></div>
<div class="suggest-tags">
<button
v-for="item in hotWords"
:key="item"
type="button"
class="suggest-tag"
@click="$emit('pick', item)"
>{{ item }}</button>
</div>
</div>
</template>
</div>
</template>
<script>
export default {
name: "SearchSuggest",
props: {
keyword: { type: String, default: "" },
history: { type: Array, default: () => [] },
hotWords: { type: Array, default: () => [] },
suggestions: { type: Array, default: () => [] },
suggesting: { type: Boolean, default: false },
},
emits: ["pick", "clear"],
methods: {
parts(text) {
const value = String(text || "");
const key = this.keyword;
if (!key) return [{ text: value, hit: false }];
const index = value.toLowerCase().indexOf(key.toLowerCase());
if (index < 0) return [{ text: value, hit: false }];
return [
{ text: value.slice(0, index), hit: false },
{ text: value.slice(index, index + key.length), hit: true },
{ text: value.slice(index + key.length), hit: false },
].filter((part) => part.text);
},
},
};
</script>
<style scoped lang="scss">
.suggest-section + .suggest-section {
margin-top: 4px;
padding-top: 4px;
border-top: 1px solid #f5f5f5;
}
.suggest-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 16px;
color: #999;
font-size: 13px;
}
.suggest-clear {
border: none;
background: transparent;
color: #999;
font-size: 13px;
cursor: pointer;
}
.suggest-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 0 16px 8px;
}
.suggest-tag {
max-width: 180px;
padding: 4px 10px;
border: none;
border-radius: 14px;
background: #f5f5f5;
color: #333;
font-size: 13px;
line-height: 20px;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.suggest-item {
display: block;
width: 100%;
padding: 8px 16px;
border: none;
background: transparent;
color: #333;
font-size: 14px;
line-height: 22px;
text-align: left;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
background: #fafafa;
}
.hit {
color: $theme_color;
}
}
.suggest-empty {
padding: 10px 16px;
color: #999;
font-size: 13px;
}
</style>