feat:增加博客搜索模块
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { cloudService } from './index'
|
||||
import { IBlog, IBlogCategory, IBlogComment, IBlogStats, IQueryBlog } from '@/types/blog'
|
||||
import { IBlog, IBlogCategory, IBlogComment, IBlogSearch, IBlogStats, IQueryBlog } from '@/types/blog'
|
||||
|
||||
import qs from 'qs'
|
||||
import { IPageRecord } from '@/types'
|
||||
@@ -11,6 +11,13 @@ export const queryBlogApi = (currentPage: number, pageSize: number): Promise<IPa
|
||||
params: { currentPage, pageSize }
|
||||
})
|
||||
|
||||
export const searchBlogApi = (keyword: string): Promise<IBlogSearch[]> =>
|
||||
cloudService({
|
||||
url: '/blog-api/blog/search',
|
||||
method: 'get',
|
||||
params: { keyword }
|
||||
})
|
||||
|
||||
export const queryBlogByConditionApi = (query: IQueryBlog): Promise<IBlog[]> =>
|
||||
cloudService({
|
||||
url: '/blog-api/blog/condition',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<span class="value">{{ blogStore.blogStats.blogCount }}</span>
|
||||
</el-menu-item>
|
||||
|
||||
<el-menu-item>
|
||||
<el-menu-item index="/search">
|
||||
<i class="fa fa-search fa-fw" />
|
||||
<span class="menu-title">搜索</span>
|
||||
</el-menu-item>
|
||||
|
||||
@@ -12,7 +12,7 @@ const blogRoutes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/overview.vue')
|
||||
},
|
||||
{
|
||||
path: '/overview/detail:id',
|
||||
path: '/overview/detail/:id',
|
||||
name: 'DetailId',
|
||||
component: () => import('@/views/detail[id].vue')
|
||||
}
|
||||
@@ -28,7 +28,7 @@ const blogRoutes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/category.vue')
|
||||
},
|
||||
{
|
||||
path: '/category:name',
|
||||
path: '/category/:name',
|
||||
name: 'CategoryName',
|
||||
component: () => import('@/views/category[name].vue')
|
||||
}
|
||||
@@ -44,6 +44,17 @@ const blogRoutes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/archive.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/search',
|
||||
redirect: '/search/index',
|
||||
component: BlogLayout,
|
||||
children: [
|
||||
{
|
||||
path: '/search/index',
|
||||
component: () => import('@/views/search.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import {
|
||||
IBlog, IBlogCategory, IBlogComment,
|
||||
IBlogLatest, IBlogNestedComment, IBlogStats, INewBlog, IQueryBlog
|
||||
IBlogLatest, IBlogNestedComment, IBlogSearch, IBlogStats, INewBlog, IQueryBlog
|
||||
} from '@/types/blog'
|
||||
import {
|
||||
addBlogCommentApi,
|
||||
queryBlogApi, queryBlogByConditionApi, queryBlogByIdApi,
|
||||
queryBlogCategoryApi, queryBlogCommentApi, queryBlogStatsApi, queryLatestBlogApi
|
||||
queryBlogCategoryApi, queryBlogCommentApi, queryBlogStatsApi, queryLatestBlogApi, searchBlogApi
|
||||
} from '@/apis/blog.ts'
|
||||
import { IHandleApi } from 'vue3-common/types'
|
||||
import { ImagePathEnum } from '@/types'
|
||||
@@ -17,6 +17,7 @@ export const useBlogStore = defineStore('blog', {
|
||||
blogList: [] as IBlog[],
|
||||
blogCommentList: [] as IBlogComment[],
|
||||
blogNestedCommentList: [] as IBlogNestedComment[],
|
||||
searchBlogList: [] as IBlogSearch[],
|
||||
currentBlogComment: {
|
||||
id: 0,
|
||||
blogId: 0,
|
||||
@@ -50,6 +51,8 @@ export const useBlogStore = defineStore('blog', {
|
||||
blogStats: {} as IBlogStats,
|
||||
latestBlogList: [] as IBlogLatest[],
|
||||
queryBlog: {} as IQueryBlog,
|
||||
searchBlogKeyword: '',
|
||||
isSearching: false,
|
||||
blogImagePath: ImagePathEnum.BLOG,
|
||||
blogApiType: 'ADD' as IHandleApi,
|
||||
pageInfo: {
|
||||
@@ -83,6 +86,11 @@ export const useBlogStore = defineStore('blog', {
|
||||
async queryBlogComment(blogId: number) {
|
||||
this.blogCommentList = await queryBlogCommentApi(blogId)
|
||||
},
|
||||
async searchBlog(keyword: string) {
|
||||
this.isSearching = true
|
||||
this.searchBlogList = await searchBlogApi(keyword)
|
||||
this.isSearching = false
|
||||
},
|
||||
async addBlogComment(blogComment: IBlogComment) {
|
||||
await addBlogCommentApi(blogComment)
|
||||
await this.queryBlogById(blogComment.blogId)
|
||||
|
||||
@@ -82,3 +82,10 @@ export interface IBlogNestedComment {
|
||||
comment: IBlogComment;
|
||||
replies: IBlogComment[];
|
||||
}
|
||||
|
||||
export interface IBlogSearch {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
highlight: string;
|
||||
}
|
||||
|
||||
107
src/views/search.vue
Normal file
107
src/views/search.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="blog-search blog-section card-item">
|
||||
<el-input
|
||||
v-model="blogStore.searchBlogKeyword"
|
||||
@input="searchBlogClick"
|
||||
clearable
|
||||
placeholder="搜索博客文章..."
|
||||
/>
|
||||
|
||||
<div v-if="blogStore.searchBlogList.length > 0"
|
||||
v-loading="blogStore.isSearching"
|
||||
element-loading-text="正在搜索中..."
|
||||
class="blog-results">
|
||||
<h3 class="results-title">找到 {{ blogStore.searchBlogList.length }} 篇相关博客</h3>
|
||||
|
||||
<div v-for="(blog, index) in blogStore.searchBlogList"
|
||||
:key="index" class="blog-card card-item">
|
||||
<h4 class="blog-title">{{ blog.title }}</h4>
|
||||
<div class="blog-excerpt" v-html="blog.highlight" />
|
||||
|
||||
<router-link :to="`/overview/detail/${blog.id}`" class="read-more">阅读全文</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useBlogStore } from '@/store/blog'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const blogStore = useBlogStore()
|
||||
|
||||
onMounted(() => {
|
||||
blogStore.searchBlogKeyword = ''
|
||||
blogStore.searchBlogList = []
|
||||
})
|
||||
|
||||
const searchBlogClick = async (value: string) => {
|
||||
if (!value) {
|
||||
blogStore.searchBlogList = []
|
||||
return
|
||||
}
|
||||
|
||||
await blogStore.searchBlog(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.blog-search {
|
||||
padding: 20px;
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.blog-results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.results-title {
|
||||
color: #333;
|
||||
margin-bottom: 25px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.blog-card {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.blog-title {
|
||||
color: #2c3e50;
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.blog-excerpt {
|
||||
color: #34495e;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.read-more {
|
||||
display: inline-block;
|
||||
color: #009FA8;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.read-more:hover {
|
||||
color: #009FA8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.blog-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user