feat:增加博客搜索模块
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { cloudService } from './index'
|
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 qs from 'qs'
|
||||||
import { IPageRecord } from '@/types'
|
import { IPageRecord } from '@/types'
|
||||||
@@ -11,6 +11,13 @@ export const queryBlogApi = (currentPage: number, pageSize: number): Promise<IPa
|
|||||||
params: { currentPage, pageSize }
|
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[]> =>
|
export const queryBlogByConditionApi = (query: IQueryBlog): Promise<IBlog[]> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/blog-api/blog/condition',
|
url: '/blog-api/blog/condition',
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<span class="value">{{ blogStore.blogStats.blogCount }}</span>
|
<span class="value">{{ blogStore.blogStats.blogCount }}</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
<el-menu-item>
|
<el-menu-item index="/search">
|
||||||
<i class="fa fa-search fa-fw" />
|
<i class="fa fa-search fa-fw" />
|
||||||
<span class="menu-title">搜索</span>
|
<span class="menu-title">搜索</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const blogRoutes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/overview.vue')
|
component: () => import('@/views/overview.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/overview/detail:id',
|
path: '/overview/detail/:id',
|
||||||
name: 'DetailId',
|
name: 'DetailId',
|
||||||
component: () => import('@/views/detail[id].vue')
|
component: () => import('@/views/detail[id].vue')
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ const blogRoutes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/category.vue')
|
component: () => import('@/views/category.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/category:name',
|
path: '/category/:name',
|
||||||
name: 'CategoryName',
|
name: 'CategoryName',
|
||||||
component: () => import('@/views/category[name].vue')
|
component: () => import('@/views/category[name].vue')
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,17 @@ const blogRoutes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/archive.vue')
|
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 { defineStore } from 'pinia'
|
||||||
import {
|
import {
|
||||||
IBlog, IBlogCategory, IBlogComment,
|
IBlog, IBlogCategory, IBlogComment,
|
||||||
IBlogLatest, IBlogNestedComment, IBlogStats, INewBlog, IQueryBlog
|
IBlogLatest, IBlogNestedComment, IBlogSearch, IBlogStats, INewBlog, IQueryBlog
|
||||||
} from '@/types/blog'
|
} from '@/types/blog'
|
||||||
import {
|
import {
|
||||||
addBlogCommentApi,
|
addBlogCommentApi,
|
||||||
queryBlogApi, queryBlogByConditionApi, queryBlogByIdApi,
|
queryBlogApi, queryBlogByConditionApi, queryBlogByIdApi,
|
||||||
queryBlogCategoryApi, queryBlogCommentApi, queryBlogStatsApi, queryLatestBlogApi
|
queryBlogCategoryApi, queryBlogCommentApi, queryBlogStatsApi, queryLatestBlogApi, searchBlogApi
|
||||||
} from '@/apis/blog.ts'
|
} from '@/apis/blog.ts'
|
||||||
import { IHandleApi } from 'vue3-common/types'
|
import { IHandleApi } from 'vue3-common/types'
|
||||||
import { ImagePathEnum } from '@/types'
|
import { ImagePathEnum } from '@/types'
|
||||||
@@ -17,6 +17,7 @@ export const useBlogStore = defineStore('blog', {
|
|||||||
blogList: [] as IBlog[],
|
blogList: [] as IBlog[],
|
||||||
blogCommentList: [] as IBlogComment[],
|
blogCommentList: [] as IBlogComment[],
|
||||||
blogNestedCommentList: [] as IBlogNestedComment[],
|
blogNestedCommentList: [] as IBlogNestedComment[],
|
||||||
|
searchBlogList: [] as IBlogSearch[],
|
||||||
currentBlogComment: {
|
currentBlogComment: {
|
||||||
id: 0,
|
id: 0,
|
||||||
blogId: 0,
|
blogId: 0,
|
||||||
@@ -50,6 +51,8 @@ export const useBlogStore = defineStore('blog', {
|
|||||||
blogStats: {} as IBlogStats,
|
blogStats: {} as IBlogStats,
|
||||||
latestBlogList: [] as IBlogLatest[],
|
latestBlogList: [] as IBlogLatest[],
|
||||||
queryBlog: {} as IQueryBlog,
|
queryBlog: {} as IQueryBlog,
|
||||||
|
searchBlogKeyword: '',
|
||||||
|
isSearching: false,
|
||||||
blogImagePath: ImagePathEnum.BLOG,
|
blogImagePath: ImagePathEnum.BLOG,
|
||||||
blogApiType: 'ADD' as IHandleApi,
|
blogApiType: 'ADD' as IHandleApi,
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
@@ -83,6 +86,11 @@ export const useBlogStore = defineStore('blog', {
|
|||||||
async queryBlogComment(blogId: number) {
|
async queryBlogComment(blogId: number) {
|
||||||
this.blogCommentList = await queryBlogCommentApi(blogId)
|
this.blogCommentList = await queryBlogCommentApi(blogId)
|
||||||
},
|
},
|
||||||
|
async searchBlog(keyword: string) {
|
||||||
|
this.isSearching = true
|
||||||
|
this.searchBlogList = await searchBlogApi(keyword)
|
||||||
|
this.isSearching = false
|
||||||
|
},
|
||||||
async addBlogComment(blogComment: IBlogComment) {
|
async addBlogComment(blogComment: IBlogComment) {
|
||||||
await addBlogCommentApi(blogComment)
|
await addBlogCommentApi(blogComment)
|
||||||
await this.queryBlogById(blogComment.blogId)
|
await this.queryBlogById(blogComment.blogId)
|
||||||
|
|||||||
@@ -82,3 +82,10 @@ export interface IBlogNestedComment {
|
|||||||
comment: IBlogComment;
|
comment: IBlogComment;
|
||||||
replies: 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