Skip to content

Commit 4642d28

Browse files
committed
feat: add search
1 parent 5baf49a commit 4642d28

File tree

4 files changed

+238
-16
lines changed

4 files changed

+238
-16
lines changed

app/layouts/default.vue

+87-15
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,102 @@
11
<script setup lang="ts">
2-
const headerLinks = [
3-
// {
4-
// id: 'index',
5-
// label: '首页',
6-
// icon: 'i-icon-park-outline-home',
7-
// to: '/',
8-
// },
9-
// {
10-
// id: 'classify',
11-
// label: '分类',
12-
// icon: 'i-icon-park-outline-all-application',
13-
// to: '/classify',
14-
// },
15-
]
2+
const route = useRoute()
3+
const toast = useToast()
4+
5+
const isOpen = ref(false)
6+
const search = ref(route.params.keyword ?? '')
7+
function handleSearch() {
8+
if (!search.value) {
9+
toast.add({ title: '搜索内容不能为空', color: 'red' })
10+
return
11+
}
12+
navigateTo(`/search/${search.value}`, !route.path.includes('/search/')
13+
? {
14+
open: {
15+
target: '_blank',
16+
},
17+
}
18+
: {})
19+
isOpen.value = false
20+
}
1621
</script>
1722

1823
<template>
1924
<div>
20-
<UHeader :links="headerLinks">
25+
<UHeader>
2126
<template #logo>
2227
<ULink to="/">
2328
Manga Wiki
2429
</ULink>
2530
</template>
2631

32+
<template #center>
33+
<UInput
34+
v-model="search"
35+
icon="i-heroicons:magnifying-glass-20-solid"
36+
placeholder="搜索漫画..."
37+
color="white"
38+
variant="outline"
39+
:ui="{ icon: { trailing: { pointer: '' } } }"
40+
class="hidden sm:flex md:w-[320px] sm:w-[200px]"
41+
@keydown.enter="handleSearch"
42+
>
43+
<template #trailing>
44+
<UButton
45+
v-show="search !== ''"
46+
color="gray"
47+
variant="link"
48+
icon="i-heroicons-x-mark-20-solid"
49+
:padded="false"
50+
@click="search = ''"
51+
/>
52+
</template>
53+
</UInput>
54+
</template>
55+
2756
<template #right>
57+
<UButton
58+
icon="i-heroicons:magnifying-glass-20-solid"
59+
color="gray"
60+
variant="ghost"
61+
class="flex sm:hidden"
62+
@click="isOpen = true"
63+
/>
64+
65+
<UModal
66+
v-model="isOpen"
67+
:ui="{ container: 'items-start' }"
68+
>
69+
<div class="flex p-4 gap-2 w-full">
70+
<UInput
71+
v-model="search"
72+
icon="i-heroicons:magnifying-glass-20-solid"
73+
placeholder="搜索漫画..."
74+
color="white"
75+
variant="outline"
76+
:ui="{ icon: { trailing: { pointer: '' } } }"
77+
class="w-full"
78+
@keydown.enter="handleSearch"
79+
>
80+
<template #trailing>
81+
<UButton
82+
v-show="search !== ''"
83+
color="gray"
84+
variant="link"
85+
icon="i-heroicons-x-mark-20-solid"
86+
:padded="false"
87+
@click="search = ''"
88+
/>
89+
</template>
90+
</UInput>
91+
<UButton
92+
color="gray"
93+
@click="isOpen = false"
94+
>
95+
取消
96+
</UButton>
97+
</div>
98+
</UModal>
99+
28100
<UColorModeButton />
29101

30102
<UButton

app/pages/index.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ const mangaTotal = computed(() => data.value?.total ?? 0)
231231
<div class="aspect-w-3 aspect-h-4">
232232
<img
233233
loading="lazy"
234-
:src="item.images.common"
234+
:src="item.images?.common"
235235
:alt="item.name_cn || item.name || '未知'"
236236
class="rounded-md object-cover bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-800"
237237
>

app/pages/search/[keyword].vue

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<script setup lang="ts">
2+
const route = useRoute()
3+
const { y } = useWindowScroll({ behavior: 'smooth' })
4+
5+
function to({ query }: any) {
6+
return {
7+
path: route.path,
8+
query: {
9+
...route.query,
10+
...query,
11+
},
12+
}
13+
}
14+
15+
function _navigateTo({ query }: any) {
16+
for (const key in query) {
17+
if (isNil(query[key]))
18+
query[key] = ''
19+
}
20+
navigateTo(to({ query }))
21+
y.value = 0
22+
}
23+
24+
interface RequestManga {
25+
list: any[]
26+
results: number
27+
}
28+
29+
const keyword = route.params.keyword
30+
31+
const page = ref(Number(route.query.page || 1))
32+
function handleChangePage(value: number) {
33+
_navigateTo({
34+
query: { page: value },
35+
})
36+
}
37+
38+
const { data, status } = await useFetch<RequestManga>(`/api/search/subject/${keyword}`, {
39+
query: computed(() => {
40+
return {
41+
type: 1,
42+
responseGroup: 'large',
43+
max_results: 24,
44+
start: (page.value - 1) * 24 + 1,
45+
}
46+
}),
47+
})
48+
const isLoading = computed(() => status.value === 'pending')
49+
watch(isLoading, (val: boolean) => {
50+
if (val) {
51+
y.value = 0
52+
}
53+
})
54+
const mangaList = computed(() => data.value?.list ?? [])
55+
const mangaTotal = computed(() => data.value?.results ?? 0)
56+
</script>
57+
58+
<template>
59+
<div>
60+
<div class="my-4">
61+
以下为
62+
<span class="text-primary-500 dark:text-primary-400">“{{ route.params.keyword }}”</span>
63+
的搜索结果:
64+
</div>
65+
66+
<!-- 内容区域 -->
67+
<div class="my-4">
68+
<!-- 加载状态 -->
69+
<div
70+
v-if="isLoading"
71+
class="flex flex-col h-80 items-center justify-center rounded-md"
72+
>
73+
<UIcon
74+
name="i-icon-park-outline-loading-four"
75+
class="animate-spin text-[28px]"
76+
/>
77+
<div class="mt-2 text-sm">
78+
加载中...
79+
</div>
80+
</div>
81+
82+
<div
83+
v-if="!isLoading && !mangaTotal"
84+
class="flex flex-col h-80 items-center justify-center rounded-md"
85+
>
86+
<UIcon
87+
name="i-icon-park-outline-box"
88+
class="text-[28px]"
89+
/>
90+
<div class="mt-2 text-sm">
91+
暂无内容
92+
</div>
93+
</div>
94+
95+
<!-- 漫画列表 -->
96+
<div
97+
v-if="!isLoading"
98+
class="grid grid-cols-3 sm:grid-cols-6 xl:grid-cols-8 gap-4 items-start"
99+
>
100+
<ULink
101+
v-for="item in mangaList"
102+
:key="item.id"
103+
:to="`/manga/${item.id}`"
104+
target="_blank"
105+
class="group text-left"
106+
>
107+
<div class="aspect-w-3 aspect-h-4">
108+
<img
109+
loading="lazy"
110+
:src="item.images?.common"
111+
:alt="item.name_cn || item.name || '未知'"
112+
class="rounded-md object-cover bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-800"
113+
>
114+
</div>
115+
<div class="mt-1 text-gray-900 dark:text-white font-bold truncate group-hover:text-primary-500 group-hover:dark:text-primary-400">
116+
{{ item.name_cn || item.name || '未知' }}
117+
</div>
118+
<!-- <div class="text-sm truncate">
119+
{{ findInfoBox(item.infobox, '作者') !== '未知' ? findInfoBox(item.infobox, '作者') : findInfoBox(item.infobox, '作画') }}
120+
</div> -->
121+
</ULink>
122+
</div>
123+
124+
<!-- 分页 -->
125+
<UPagination
126+
v-if="!isLoading && mangaTotal > 0"
127+
v-model="page"
128+
:total="mangaTotal"
129+
:page-count="24"
130+
:to="(value: number) => to({
131+
query: {
132+
page: value,
133+
},
134+
})"
135+
class="mt-4"
136+
@update:model-value="handleChangePage"
137+
/>
138+
</div>
139+
</div>
140+
</template>
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default defineEventHandler(async (event) => {
2+
const query = getQuery(event)
3+
const keywords = getRouterParam(event, 'keyword')
4+
return $fetch(`https://api.bgm.tv/search/subject/${keywords}`, {
5+
headers: {
6+
'User-Agent': 'Mikasa33/manga-wiki (https://github.com/Mikasa33/manga-wiki)',
7+
},
8+
query,
9+
})
10+
})

0 commit comments

Comments
 (0)