VueHooks工具
vue
Fuse搜索工具
text
1import Fuse from 'fuse.js'
2import type {IFuseOptions} from "fuse.js";
3
4
5export default function useFuse<T>(list: T[], options: IFuseOptions<T>) {
6
7 const fuse = new Fuse<T>(list, options)
8
9 function search(keyword: string, limit: number = Infinity): T[] {
10 if (!keyword) return list
11 const results = fuse.search(keyword, {limit})
12 return results.map(r => r.item)
13 }
14
15 /**
16 * fuseSearch - 单次模糊搜索工具函数
17 *
18 * @param list 数据源
19 * @param keyword 搜索关键词
20 * @param options Fuse.js 配置
21 * @param limit 限制返回数量,默认 Infinity
22 */
23 function fuseSearch<T>(
24 list: T[],
25 keyword: string,
26 options: IFuseOptions<T>,
27 limit: number = Infinity
28 ): T[] {
29 if (!keyword) return list
30
31 const fuse = new Fuse<T>(list, options)
32 const results = fuse.search(keyword, {limit})
33 return results.map(r => r.item)
34 }
35
36 return {search, fuseSearch}
37}
38text
1import { defineEventHandler } from 'h3'
2import { db } from '~~/server/utils/db'
3
4function capitalize(str: string): string {
5 if (!str) return ''
6 return str.charAt(0).toUpperCase() + str.slice(1)
7}
8
9interface BlogItem {
10 id: number
11 title: string
12 summary: string
13 subCategory: string
14 author: string
15 category: string
16}
17
18interface SubCategoryGroup {
19 subCategory: string
20 list: BlogItem[]
21}
22
23interface CategoryGroup {
24 category: string
25 subCategories: SubCategoryGroup[]
26}
27
28export default defineWrappedResponseHandler(
29 defineEventHandler(async () => {
30 type DbRow = {
31 id: number
32 title: string
33 author: string
34 tags: string
35 summary: string
36 sub_category: string
37 category: string
38 }
39
40 const blogs: DbRow[] = await db('blog_posts as b')
41 .select(
42 'b.id',
43 'b.title',
44 'b.author',
45 'b.tags',
46 'b.summary',
47 'b.sub_category',
48 'b.category'
49 )
50 .where('b.is_published', 1)
51
52 const temp: Record<string, Record<string, BlogItem[]>> = {}
53
54 for (const row of blogs) {
55 const category = row.category || '未分类'
56 const sub = capitalize(row.sub_category || '未分组')
57
58 if (!temp[category]) temp[category] = {}
59 if (!temp[category][sub]) temp[category][sub] = []
60
61 temp[category][sub].push({
62 id: row.id,
63 title: row.title,
64 summary: row.summary,
65 subCategory: sub,
66 author: row.author,
67 category: category
68 })
69 }
70
71 // 排序 + 转换成数组结构
72 const result: CategoryGroup[] = Object.entries(temp).map(
73 ([category, subMap]) => {
74 const subCategories: SubCategoryGroup[] = Object.entries(subMap).map(
75 ([subCategory, list]) => ({
76 subCategory,
77 list: list.sort((a, b) =>
78 a.title.localeCompare(b.title, 'zh-CN', { sensitivity: 'base' })
79 )
80 })
81 )
82
83 return {
84 category,
85 subCategories
86 }
87 }
88 )
89
90 return result
91 })
92)
93
94
95import {defineEventHandler} from 'h3'
96import {db} from '~~/server/utils/db'
97
98function capitalize(str: string): string {
99 if (!str) return ''
100 return str.charAt(0).toUpperCase() + str.slice(1)
101}
102
103export default defineWrappedResponseHandler(defineEventHandler(async (event) => {
104 const query = getQuery(event) // 从 URL 获取参数,比如 ?category=tech
105 const category = query.category as string
106 const blogs = await db('blog_posts as b')
107 .select('b.id', 'b.title', 'b.author', 'b.tags', 'b.summary', 'b.sub_category')
108 .where('b.category', category).andWhere('b.is_published', 1)
109 const grouped: Record<string, {
110 id: number;
111 title: string,
112 summary: string,
113 sub_category: string,
114 author: string
115 }[]> = {}
116 for (const row of blogs) {
117 const tag = capitalize(row.tags)
118 if (!grouped[tag]) {
119 grouped[tag] = []
120 }
121 grouped[tag].push({
122 id: row.id,
123 title: row.title,
124 summary: row.summary,
125 sub_category: row.sub_category,
126 author: row.author
127 })
128 }
129 // 对每个 tag 下的数组按 title 排序
130 for (const tag in grouped) {
131 grouped[tag].sort((a, b) => a.title.localeCompare(b.title, 'zh-CN'))
132 }
133 return grouped;
134}))
135
136
137// api/blog/star.post.ts
138// http POST http://localhost:3000/api/star id:=1 action=inc
139export default defineWrappedResponseHandler(
140 defineEventHandler(async (event) => {
141 const body = await readBody<{ id: number; action: 'inc' | 'dec' }>(event)
142 if (!body.id) {
143 throw new Error('缺少参数 id')
144 }
145 if (!['inc', 'dec'].includes(body.action)) {
146 throw new Error('action 必须是 inc 或 dec')
147 }
148 // 更新 SQL
149 const query = db('blog_posts').where('id', body.id)
150 if (body.action === 'inc') {
151 await query.increment('star', 1)
152 } else {
153 await query.decrement('star', 1)
154 }
155 // 返回最新 star 数
156 const updated = await db('blog_posts')
157 .select('id', 'title', 'star')
158 .where('id', body.id)
159 .first()
160 return updated
161 })
162)
163