feat: 优化搜索功能并添加历史记录功能

1. 新增历史记录API模块,支持获取和添加搜索历史
2. 优化搜索组件交互体验
3. 更新搜索推荐功能,支持历史记录展示
4. 修复搜索框焦点控制问题
5. 优化搜索状态管理逻辑
This commit is contained in:
Huangzhe
2025-05-18 11:40:32 +08:00
parent 036fe0b4f7
commit 51a9fd085e
7 changed files with 128 additions and 38 deletions

38
src/api/history/index.ts Normal file
View File

@@ -0,0 +1,38 @@
import request from '@/utils/request';
/**
* 获取历史记录
* @returns
*/
export function fetchHistory() {
return request({
url: `/console/hot_search_history/list`,
method: 'get'
});
}
/**
* 增加历史记录
* @param keyword {string}
* @returns
*/
export function addHistory(keyword: string) {
return request({
url: `/console/hot_search_history/add`,
method: 'post',
data: {
keyword: keyword
}
});
}
/**
* 删除所有的历史记录
* @returns
*/
export function clearHistory() {
return request({
url: `/console/hot_search_history/clear`,
method: 'post'
});
}

View File

@@ -1,6 +1,7 @@
<template>
<section class="search-container">
<van-search
v-focus="focus"
v-model="value"
:placeholder="placeholder"
style="--van-search-padding: 0"
@@ -12,8 +13,12 @@
</template>
<script setup lang="ts">
import { vFocus } from '@/utils/directives/useVFocus';
// 搜索的关键词
const value = defineModel<string>('value', { required: true });
// focus 是否默认调出键盘
const focus = defineModel('focus', { default: false });
/**
* @description 搜索方法
*/

View File

@@ -1,10 +1,11 @@
import type { Directive } from 'vue';
import type { Directive, DirectiveBinding } from 'vue';
/**
* 自定义指令,用于在元素挂载后自动获取焦点
*/
export const vFocus: Directive = {
mounted(el: HTMLInputElement) {
el.focus();
mounted(el: HTMLInputElement, binding: DirectiveBinding<any, string>) {
const value = binding.value;
value && el.focus();
}
};

View File

@@ -1,6 +1,6 @@
import { nextTick, ref, watch } from 'vue';
import { getSurveysPage } from '@/api/home';
import { saveSearchHistory } from '@/views/HomeSearch/components/Recommend/hooks/useRecommend';
import { initialHistory, saveSearchHistory } from '@/views/HomeSearch/components/Recommend/hooks/useRecommend';
import { getSearchInfo } from '@/api/home/search';
// 问卷
@@ -23,7 +23,7 @@ const templates = ref();
// banners
const banners = ref();
async function handleSearch () {
async function handleSearch() {
// loading 状态开启
// loading.value = true;
const params = {
@@ -54,13 +54,16 @@ async function handleSearch () {
/**
* 更新 搜索关键字信息
*/
async function updateKeyword (key?: string) {
async function updateKeyword(key?: string) {
// 如果 key 存在,则更新关键字
if (key) keyword.value = key;
// 排除边界条件
if (!keyword.value) return;
if (!keyword.value) {
initialHistory()
loading.value = false;
return;
}
// 重新获取数据
await handleSearch();
@@ -76,7 +79,7 @@ async function updateKeyword (key?: string) {
*
* @param value
*/
function updatePageCount (value: number) {
function updatePageCount(value: number) {
pageCount.value = value;
}
@@ -105,4 +108,4 @@ export {
index,
templates,
banners
};
};

View File

@@ -19,7 +19,7 @@ onMounted(() => {
<template>
<section class="search-container">
<div class="search">
<search v-model:value="keyword as string" :search="handleSearch" />
<search :focus="true" v-model:value="keyword as string" :search="handleSearch" />
</div>
<!-- 广告区域 -->
<image-slider :banners="banners" v-if="banners?.length" />
@@ -27,10 +27,16 @@ onMounted(() => {
<section class="result" v-if="loading">
<!-- 我的任务区域 -->
<layout v-if="visible.mineSurvey" title="我的任务">
<template #title>
<span class="title">我的任务</span>
</template>
<mine-survey />
</layout>
<!-- 更多模板区域 -->
<layout v-if="visible.templateMarket" title="问卷模板">
<template #title>
<span class="title">问卷模板</span>
</template>
<template-market />
</layout>
</section>
@@ -44,6 +50,18 @@ onMounted(() => {
<style scoped lang="scss">
@use '@/assets/css/theme';
.title {
overflow-wrap: break-word;
color: rgba(0, 0, 0, 1);
font-size: 14px;
font-family: PingFangSC-Medium;
font-weight: 500;
text-align: left;
white-space: nowrap;
line-height: 20px;
margin: 28px 297px 0 2px;
}
.search-container {
.search {
padding-top: 10px;

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import Layout from '@/components/Layout/CommonLayout.vue';
import { list, useFetchRecommon, history } from './hooks/useRecommend';
import { list, useFetchRecommon, history, delHistory } from './hooks/useRecommend';
import { Delete } from '@element-plus/icons-vue';
import { handleSearch } from '../../Hooks/useSurveySearch';
@@ -10,6 +10,9 @@ useFetchRecommon();
<template>
<section class="recommend-container">
<layout title="热门搜索">
<template #title>
<el-text class="title">热门搜索</el-text>
</template>
<el-space>
<el-tag style="color: black" color="white" :hit="false" round v-for="tag in list">{{
tag.key_word
@@ -20,9 +23,9 @@ useFetchRecommon();
<layout>
<template #title>
<section class="history-title">
<el-text>历史记录</el-text>
<el-text class="title">历史记录</el-text>
<div>
<el-icon><Delete /></el-icon>
<el-icon @click="delHistory"><Delete /></el-icon>
</div>
</section>
</template>
@@ -44,6 +47,15 @@ useFetchRecommon();
<style scoped lang="scss">
@use '@/assets/css/theme';
.title {
font-size: 25px;
font-weight: 800;
margin-bottom: 10px;
color: rgba(102, 102, 102, 1);
font-size: 13px;
font-family: PingFangSC;
}
.recommend-container {
margin-top: 20px;
padding: 0 (theme.$gap * 2);

View File

@@ -1,36 +1,49 @@
import { hotSearch } from "@/api/home";
import type { HotSearch } from "@/api/types/hotSearch";
import { ref } from "vue";
import { addHistory, clearHistory, fetchHistory } from '@/api/history';
import { hotSearch } from '@/api/home';
import type { HotSearch } from '@/api/types/hotSearch';
import { ref } from 'vue';
// 从服务器接受的列表内容
const list = ref<HotSearch[]>([])
const list = ref<HotSearch[]>([]);
// 历史列表
const history = ref<Set<string>>()
initialHistory()
const history = ref<Set<string>>(new Set());
initialHistory();
async function useFetchRecommon () {
const { data } = await hotSearch()
list.value = data.data
async function useFetchRecommon() {
const { data } = await hotSearch();
list.value = data.data;
}
// 初始化历史
function initialHistory () {
const historyStr = localStorage.getItem('history')
if (historyStr) {
history.value = new Set(JSON.parse(historyStr))
} else {
history.value = new Set()
}
async function initialHistory() {
// 屏蔽从storage获取记录的方式
// const historyStr = localStorage.getItem('history')
// if (historyStr) {
// history.value = new Set(JSON.parse(historyStr))
// } else {
// history.value = new Set()
// }
const { data } = await fetchHistory();
console.log(data);
data.data.forEach((item: { id: number; keyword: string }) => {
history.value.add(item.keyword);
});
}
// 保存历史
function saveSearchHistory (val: string) {
if (!history.value) {
history.value = new Set()
}
history.value.add(val)
localStorage.setItem('history', JSON.stringify(Array.from(history.value)))
async function saveSearchHistory(val: string) {
// 屏蔽从storage获取记录的方式
// if (!history.value) {
// history.value = new Set();
// }
// history.value.add(val);
// localStorage.setItem('history', JSON.stringify(Array.from(history.value)));
addHistory(val);
}
export { useFetchRecommon, list,saveSearchHistory, history }
async function delHistory() {
await clearHistory();
}
export { useFetchRecommon, list, saveSearchHistory, history, delHistory,initialHistory };