热搜功能实现
1. 新增热搜类型定义 HotSearchItem 接口 2. 新增通用响应类型 ApiResponse 和 AxiosResponse 接口 3. 实现热搜列表展示功能 4. 实现搜索历史记录保存和展示功能 5. 优化搜索逻辑,添加搜索后状态更新
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -32,7 +32,7 @@
|
|||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "Vue.volar",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"css.validate": false, //用来校验CSS文件中的语法错误和潜在的问题
|
"css.validate": false, //用来校验CSS文件中的语法错误和潜在的问题
|
||||||
|
|||||||
5
components.d.ts
vendored
5
components.d.ts
vendored
@@ -33,7 +33,6 @@ declare module 'vue' {
|
|||||||
RichText: typeof import('./src/components/RichText.vue')['default']
|
RichText: typeof import('./src/components/RichText.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SimpleLayout: typeof import('./src/components/Layout/SimpleLayout.vue')['default']
|
|
||||||
VanActionSheet: typeof import('vant/es')['ActionSheet']
|
VanActionSheet: typeof import('vant/es')['ActionSheet']
|
||||||
VanButton: typeof import('vant/es')['Button']
|
VanButton: typeof import('vant/es')['Button']
|
||||||
VanCard: typeof import('vant/es')['Card']
|
VanCard: typeof import('vant/es')['Card']
|
||||||
@@ -41,11 +40,8 @@ declare module 'vue' {
|
|||||||
VanCellGroup: typeof import('vant/es')['CellGroup']
|
VanCellGroup: typeof import('vant/es')['CellGroup']
|
||||||
VanCheckbox: typeof import('vant/es')['Checkbox']
|
VanCheckbox: typeof import('vant/es')['Checkbox']
|
||||||
VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
|
VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
|
||||||
VanCol: typeof import('vant/es')['Col']
|
|
||||||
VanDivider: typeof import('vant/es')['Divider']
|
VanDivider: typeof import('vant/es')['Divider']
|
||||||
VanField: typeof import('vant/es')['Field']
|
VanField: typeof import('vant/es')['Field']
|
||||||
VanGrid: typeof import('vant/es')['Grid']
|
|
||||||
VanGridItem: typeof import('vant/es')['GridItem']
|
|
||||||
VanIcon: typeof import('vant/es')['Icon']
|
VanIcon: typeof import('vant/es')['Icon']
|
||||||
VanList: typeof import('vant/es')['List']
|
VanList: typeof import('vant/es')['List']
|
||||||
VanNavBar: typeof import('vant/es')['NavBar']
|
VanNavBar: typeof import('vant/es')['NavBar']
|
||||||
@@ -54,7 +50,6 @@ declare module 'vue' {
|
|||||||
VanPopup: typeof import('vant/es')['Popup']
|
VanPopup: typeof import('vant/es')['Popup']
|
||||||
VanRadio: typeof import('vant/es')['Radio']
|
VanRadio: typeof import('vant/es')['Radio']
|
||||||
VanRadioGroup: typeof import('vant/es')['RadioGroup']
|
VanRadioGroup: typeof import('vant/es')['RadioGroup']
|
||||||
VanRow: typeof import('vant/es')['Row']
|
|
||||||
VanSearch: typeof import('vant/es')['Search']
|
VanSearch: typeof import('vant/es')['Search']
|
||||||
VanSpace: typeof import('vant/es')['Space']
|
VanSpace: typeof import('vant/es')['Space']
|
||||||
VanStepper: typeof import('vant/es')['Stepper']
|
VanStepper: typeof import('vant/es')['Stepper']
|
||||||
|
|||||||
@@ -75,3 +75,14 @@ export function saveTemplates(sn, data) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取推荐列表
|
||||||
|
* @returns { import('src/api/types/response').HotSearchResponse}
|
||||||
|
*/
|
||||||
|
export function hotSearch() {
|
||||||
|
return request({
|
||||||
|
url: `/console/hot_search/list`,
|
||||||
|
method: 'post',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
38
src/api/types/hotSearch.d.ts
vendored
Normal file
38
src/api/types/hotSearch.d.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 热搜项类型定义
|
||||||
|
*/
|
||||||
|
export interface HotSearchItem {
|
||||||
|
/** 热搜ID */
|
||||||
|
id: number;
|
||||||
|
/** 热搜序列号 */
|
||||||
|
sn: string;
|
||||||
|
/** 热搜状态 */
|
||||||
|
status: number;
|
||||||
|
/** 创建时间 */
|
||||||
|
created_at: string;
|
||||||
|
/** 更新时间 */
|
||||||
|
updated_at: string;
|
||||||
|
/** 删除时间,为null表示未删除 */
|
||||||
|
deleted_at: null | string;
|
||||||
|
/** 热搜关键词 */
|
||||||
|
key_word: string;
|
||||||
|
/** 显示顺序 */
|
||||||
|
display_order: number;
|
||||||
|
/** 创建用户ID */
|
||||||
|
created_user_id: number;
|
||||||
|
/** 更新用户ID */
|
||||||
|
updated_user_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 热搜列表响应类型
|
||||||
|
*/
|
||||||
|
export interface HotSearchListResponse {
|
||||||
|
list: HotSearchItem[];
|
||||||
|
total?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 兼容旧类型
|
||||||
|
*/
|
||||||
|
type HotSearch = HotSearchItem;
|
||||||
95
src/api/types/response.d.ts
vendored
Normal file
95
src/api/types/response.d.ts
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { HotSearchItem } from './hotSearch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页元数据接口
|
||||||
|
*/
|
||||||
|
export interface MetaData {
|
||||||
|
/** 起始记录 */
|
||||||
|
from: number;
|
||||||
|
/** 总记录数 */
|
||||||
|
total: number;
|
||||||
|
/** 当前页码 */
|
||||||
|
current_page: number;
|
||||||
|
/** 最后页码 */
|
||||||
|
last_page: number;
|
||||||
|
/** 每页记录数 */
|
||||||
|
per_page: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用API响应接口
|
||||||
|
*/
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
/** 响应状态码,0表示成功 */
|
||||||
|
code: number;
|
||||||
|
/** 响应消息,成功时为null */
|
||||||
|
message: string | null;
|
||||||
|
/** 响应数据 */
|
||||||
|
data: T;
|
||||||
|
/** 分页元数据,可选 */
|
||||||
|
meta?: MetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Axios响应头部接口
|
||||||
|
*/
|
||||||
|
export interface AxiosResponseHeaders {
|
||||||
|
'content-type': string;
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Axios配置接口
|
||||||
|
*/
|
||||||
|
export interface AxiosConfig {
|
||||||
|
transitional: {
|
||||||
|
silentJSONParsing: boolean;
|
||||||
|
forcedJSONParsing: boolean;
|
||||||
|
clarifyTimeoutError: boolean;
|
||||||
|
};
|
||||||
|
adapter: string[];
|
||||||
|
transformRequest: any[];
|
||||||
|
transformResponse: any[];
|
||||||
|
timeout: number;
|
||||||
|
xsrfCookieName: string;
|
||||||
|
xsrfHeaderName: string;
|
||||||
|
maxContentLength: number;
|
||||||
|
maxBodyLength: number;
|
||||||
|
env: Record<string, any>;
|
||||||
|
headers: {
|
||||||
|
Accept: string;
|
||||||
|
'Content-Type': string;
|
||||||
|
Authorization: string;
|
||||||
|
Source: string;
|
||||||
|
remoteIp: string;
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
baseURL: string;
|
||||||
|
url: string;
|
||||||
|
method: string;
|
||||||
|
allowAbsoluteUrls: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整的Axios响应接口
|
||||||
|
*/
|
||||||
|
export interface AxiosResponse<T> {
|
||||||
|
/** 响应数据 */
|
||||||
|
data: ApiResponse<T>;
|
||||||
|
/** HTTP状态码 */
|
||||||
|
status: number;
|
||||||
|
/** HTTP状态文本 */
|
||||||
|
statusText: string;
|
||||||
|
/** 响应头 */
|
||||||
|
headers: AxiosResponseHeaders;
|
||||||
|
/** 请求配置 */
|
||||||
|
config: AxiosConfig;
|
||||||
|
/** 请求对象 */
|
||||||
|
request: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 热搜列表响应类型
|
||||||
|
*/
|
||||||
|
export type HotSearchResponse = AxiosResponse<HotSearchItem[]>;
|
||||||
@@ -48,7 +48,7 @@ function useSetPieChart(
|
|||||||
|
|
||||||
// 检测边界范围 dom 和 data 是否存在
|
// 检测边界范围 dom 和 data 是否存在
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log(dom);
|
// console.log(dom);
|
||||||
if (!dom || data.length === 0) return;
|
if (!dom || data.length === 0) return;
|
||||||
// 在 dom 挂载之后,显示饼图
|
// 在 dom 挂载之后,显示饼图
|
||||||
pieChart.value = chart.init(dom.value);
|
pieChart.value = chart.init(dom.value);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { nextTick, ref, watch } from 'vue';
|
import { nextTick, ref, watch } from 'vue';
|
||||||
import { getSurveysPage } from '@/api/home';
|
import { getSurveysPage } from '@/api/home';
|
||||||
|
import { saveSearchHistory } from "@/views/HomeSearch/components/Recommend/hooks/useRecommend"
|
||||||
|
|
||||||
// 问卷
|
// 问卷
|
||||||
const surveys = ref([]);
|
const surveys = ref([]);
|
||||||
@@ -17,7 +18,7 @@ const dirty = ref(true);
|
|||||||
// loading 状态
|
// loading 状态
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
async function handleSearch() {
|
async function handleSearch () {
|
||||||
// loading 状态开启
|
// loading 状态开启
|
||||||
// loading.value = true;
|
// loading.value = true;
|
||||||
const params = {
|
const params = {
|
||||||
@@ -43,21 +44,25 @@ async function handleSearch() {
|
|||||||
/**
|
/**
|
||||||
* 更新 搜索关键字信息
|
* 更新 搜索关键字信息
|
||||||
*/
|
*/
|
||||||
async function updateKeyword() {
|
async function updateKeyword () {
|
||||||
// 排除边界条件
|
// 排除边界条件
|
||||||
if (!keyword.value) return;
|
if (!keyword.value) return;
|
||||||
|
|
||||||
// 重新获取数据
|
// 重新获取数据
|
||||||
await handleSearch();
|
await handleSearch();
|
||||||
// 处理后置条件,点击搜索之后,索引更新,
|
// 处理后置条件,点击搜索之后,索引更新,
|
||||||
index.value = 1;
|
index.value = 1;
|
||||||
|
// 把关键词添加到 localStorage 中
|
||||||
|
saveSearchHistory(keyword.value)
|
||||||
|
// 打开页面展示状态
|
||||||
|
loading.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
function updatePageCount(value: number) {
|
function updatePageCount (value: number) {
|
||||||
pageCount.value = value;
|
pageCount.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,3 +83,4 @@ watch(index, async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export { keyword, updateKeyword as handleSearch, lastIndex, loading, surveys, index };
|
export { keyword, updateKeyword as handleSearch, lastIndex, loading, surveys, index };
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,16 @@
|
|||||||
import Search from '@/components/Search/Index.vue';
|
import Search from '@/components/Search/Index.vue';
|
||||||
import TemplateMarket from '@/views/HomeSearch/components/TemplateMarket/Index.vue';
|
import TemplateMarket from '@/views/HomeSearch/components/TemplateMarket/Index.vue';
|
||||||
import MineSurvey from '@/views/HomeSearch/components/MineSurvey/Index.vue';
|
import MineSurvey from '@/views/HomeSearch/components/MineSurvey/Index.vue';
|
||||||
import { handleSearch, keyword } from '@/views/HomeSearch/Hooks/useSurveySearch';
|
import { handleSearch, keyword, loading } from '@/views/HomeSearch/Hooks/useSurveySearch';
|
||||||
import Layout from '@/components/Layout/CommonLayout.vue';
|
import Layout from '@/components/Layout/CommonLayout.vue';
|
||||||
import { visible } from '@/views/HomeSearch/Hooks/useHomeSearch';
|
import { visible } from '@/views/HomeSearch/Hooks/useHomeSearch';
|
||||||
|
import RecommendTag from '@/views/HomeSearch/components/Recommend/Index.vue';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 当页面取消挂载时,重置页面展示的状态
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -12,24 +19,31 @@ import { visible } from '@/views/HomeSearch/Hooks/useHomeSearch';
|
|||||||
<search v-model:value="keyword" :search="handleSearch" />
|
<search v-model:value="keyword" :search="handleSearch" />
|
||||||
<!-- 广告区域 -->
|
<!-- 广告区域 -->
|
||||||
|
|
||||||
<!-- 我的问卷区域 -->
|
<section v-if="loading">
|
||||||
<layout v-if="visible.mineSurvey" title="我的任务">
|
<!-- 我的问卷区域 -->
|
||||||
<template #content>
|
<layout v-if="visible.mineSurvey" title="我的任务">
|
||||||
<mine-survey />
|
<template #content>
|
||||||
</template>
|
<mine-survey />
|
||||||
</layout>
|
</template>
|
||||||
<!-- 更多模板区域 -->
|
</layout>
|
||||||
<layout v-if="visible.templateMarket" title="问卷模板">
|
<!-- 更多模板区域 -->
|
||||||
<template #content>
|
<layout v-if="visible.templateMarket" title="问卷模板">
|
||||||
<template-market />
|
<template #content>
|
||||||
</template>
|
<template-market />
|
||||||
</layout>
|
</template>
|
||||||
|
</layout>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section v-else>
|
||||||
|
<recommend-tag></recommend-tag>
|
||||||
|
</section>
|
||||||
<!-- <div class="search-result">-->
|
<!-- <div class="search-result">-->
|
||||||
<!-- <div class="search-result-item" v-for="item in searchResult" :key="item.id">-->
|
<!-- <div class="search-result-item" v-for="item in searchResult" :key="item.id">-->
|
||||||
<!-- {{ item.name }}-->
|
<!-- {{ item.name }}-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
36
src/views/HomeSearch/components/Recommend/Index.vue
Normal file
36
src/views/HomeSearch/components/Recommend/Index.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Layout from "@/components/Layout/CommonLayout.vue";
|
||||||
|
import { list, useFetchRecommon, history } from "./hooks/useRecommend";
|
||||||
|
|
||||||
|
useFetchRecommon()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="recommend-container">
|
||||||
|
<layout title="热门搜索">
|
||||||
|
<template #content>
|
||||||
|
<el-space>
|
||||||
|
<el-tag v-for="tag in list">{{ tag.key_word }}</el-tag>
|
||||||
|
</el-space>
|
||||||
|
</template>
|
||||||
|
</layout>
|
||||||
|
|
||||||
|
<layout title="搜索历史">
|
||||||
|
<template #content>
|
||||||
|
<el-space>
|
||||||
|
<el-tag v-for="tag in history">{{ tag }}</el-tag>
|
||||||
|
</el-space>
|
||||||
|
</template>
|
||||||
|
</layout>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "@/assets/css/theme";
|
||||||
|
|
||||||
|
.recommend-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 0 theme.$card-radius;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { hotSearch } from "@/api/home";
|
||||||
|
import type { HotSearch } from "@/api/types/hotSearch";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
// 从服务器接受的列表内容
|
||||||
|
const list = ref<HotSearch[]>([])
|
||||||
|
// 历史列表
|
||||||
|
const history = ref<Set<string>>()
|
||||||
|
initialHistory()
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存历史
|
||||||
|
function saveSearchHistory (val: string) {
|
||||||
|
if (!history.value) {
|
||||||
|
history.value = new Set()
|
||||||
|
}
|
||||||
|
history.value.add(val)
|
||||||
|
localStorage.setItem('history', JSON.stringify(Array.from(history.value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export { useFetchRecommon, list,saveSearchHistory, history }
|
||||||
@@ -10,7 +10,7 @@ import postCssPxToRem from 'postcss-pxtorem';
|
|||||||
import legacy from '@vitejs/plugin-legacy';
|
import legacy from '@vitejs/plugin-legacy';
|
||||||
// shift + alt 快速定位到对应组件
|
// shift + alt 快速定位到对应组件
|
||||||
import { codeInspectorPlugin } from 'code-inspector-plugin';
|
import { codeInspectorPlugin } from 'code-inspector-plugin';
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
// import tailwindcss from '@tailwindcss/vite';
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
// 接收 mode 参数
|
// 接收 mode 参数
|
||||||
@@ -77,7 +77,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
codeInspectorPlugin({
|
codeInspectorPlugin({
|
||||||
bundler: 'vite'
|
bundler: 'vite'
|
||||||
}),
|
}),
|
||||||
tailwindcss()
|
// tailwindcss()
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user