1
1
<template >
2
+ <h1 class =" market-title" >插件市场</h1 >
3
+ <div class =" search-container" >
4
+ <input
5
+ v-model =" searchQuery"
6
+ type =" text"
7
+ placeholder =" 🔍 搜索插件..."
8
+ class =" search-input"
9
+ @input =" filterPlugins"
10
+ />
11
+ </div >
2
12
<div class =" plugin-card-container" >
3
13
<div
4
- v-for =" (item, index) in items "
14
+ v-for =" (item, index) in filteredItems "
5
15
:key =" index"
6
16
class =" plugin-card"
7
17
:class =" { 'clickable': item.link }"
20
30
<div v-if =" item.priceLabel" class =" price-corner-tag" :class =" { 'paid': item.priceLabel === '付费' }" >
21
31
<span class =" price-corner-text" >{{ item.priceLabel }}</span >
22
32
</div >
23
- <div class =" image-footer" >
24
- <div class =" item-developer-info" >
25
- <img
26
- :src =" getGithubAvatarUrl(item.githubUser)"
27
- alt =" Developer Avatar"
28
- class =" developer-avatar"
29
- />
30
- <span class =" developer-name" >
31
- {{ item.githubUser }}
32
- </span >
33
- </div >
34
- <a
35
- v-if =" item.link"
36
- :href =" item.link"
37
- class =" no-external-icon"
38
- target =" _blank"
39
- rel =" noopener noreferrer"
40
- aria-label =" GitHub仓库"
41
- @click.stop
42
- >
43
- <Icon name =" mdi:github" color =" var(--vp-c-text-2)" />
44
- </a >
45
- <span v-else class =" built-in-label-inline" >内置</span >
46
- </div >
47
33
</div >
48
34
<div class =" card-content" >
49
- <h3 class =" card-title" >{{ item.title }}</h3 >
35
+ <div class =" card-title-row" >
36
+ <h3 class =" card-title" >{{ item.title }}</h3 >
37
+ <div class =" card-title-link" >
38
+ <a
39
+ v-if =" item.link"
40
+ :href =" item.link"
41
+ class =" no-external-icon"
42
+ target =" _blank"
43
+ rel =" noopener noreferrer"
44
+ aria-label =" GitHub仓库"
45
+ @click.stop
46
+ >
47
+ <Icon name =" mdi:github" color =" var(--vp-c-text-2)" />
48
+ </a >
49
+ <span v-else class =" built-in-label-inline" >内置</span >
50
+ </div >
51
+ </div >
50
52
<p class =" card-description" >{{ item.description }}</p >
51
53
<div class =" card-tags" >
52
54
<Badge
62
64
</template >
63
65
64
66
<script setup lang="ts">
67
+ import { ref , computed } from ' vue'
68
+
65
69
export interface PluginItem {
66
70
icon: string
67
71
title: string
68
72
description: string
69
73
tags: string []
70
74
link? : string
71
75
image? : string
72
- githubUser: string
73
76
priceLabel? : ' 免费' | ' 付费'
74
77
}
75
78
@@ -79,10 +82,23 @@ const props = withDefaults(
79
82
columns? : number
80
83
}>(),
81
84
{
82
- columns: 3
85
+ columns: 5
83
86
}
84
87
)
85
88
89
+ const searchQuery = ref (' ' )
90
+ const filteredItems = computed (() => {
91
+ if (! searchQuery .value .trim ()) {
92
+ return props .items
93
+ }
94
+ const query = searchQuery .value .toLowerCase ().trim ()
95
+ return props .items .filter (item =>
96
+ item .title .toLowerCase ().includes (query ) ||
97
+ item .description .toLowerCase ().includes (query ) ||
98
+ item .tags .some (tag => tag .toLowerCase ().includes (query ))
99
+ )
100
+ })
101
+
86
102
const colors: Record <string , TagColors > = {
87
103
' MySQL' : { color: ' #006484' , backgroundColor: ' rgba(0, 100, 132, 0.1)' , borderColor: ' rgba(0, 100, 132, 0.2)' },
88
104
' PostgreSQL' : {
@@ -92,24 +108,31 @@ const colors: Record<string, TagColors> = {
92
108
},
93
109
' 后端' : { color: ' #009485' , backgroundColor: ' rgba(0,148,133,0.1)' , borderColor: ' rgba(0,148,133,0.2)' },
94
110
' 前端' : { color: ' #a855f7' , backgroundColor: ' rgba(168, 85, 247, 0.1)' , borderColor: ' rgba(168, 85, 247, 0.2)' },
95
- };
111
+ }
96
112
97
113
const getGithubAvatarUrl = (username : string ) => {
98
- return ` https://github.com/${ username }.png?size=32 ` ;
99
- };
114
+ return ` https://github.com/${username }.png?size=32 `
115
+ }
100
116
101
117
const handleCardClick = (item : PluginItem ) => {
102
118
if (item .link ) {
103
- window .open (item .link , ' _blank' );
119
+ window .open (item .link , ' _blank' )
104
120
}
105
- };
121
+ }
106
122
</script >
107
123
108
124
<style scoped>
125
+ /* 保留所有原始样式 */
109
126
.plugin-card-container {
110
127
display : grid ;
111
- gap : 1 rem ;
128
+ padding : 2 rem 3 rem ;
112
129
grid-template-columns : repeat (1 , 1fr );
130
+ font-family : ' Segoe UI' , Tahoma , Geneva, Verdana , sans-serif ;
131
+ }
132
+
133
+ .market-title {
134
+ text-align : center ;
135
+ margin : 5rem 0 3rem ;
113
136
}
114
137
115
138
.plugin-card {
@@ -169,44 +192,40 @@ const handleCardClick = (item: PluginItem) => {
169
192
background : var (--vp-c-bg-soft );
170
193
}
171
194
172
- .image-footer {
173
- position : absolute ;
174
- bottom : 0 ;
175
- left : 0 ;
176
- right : 0 ;
195
+ .card-content {
196
+ padding : 0.75rem ;
197
+ flex-grow : 1 ;
177
198
display : flex ;
178
- justify-content : space-between ;
179
- align-items : center ;
180
- padding : 0.5rem ;
181
- background : rgba (0 , 0 , 0 , 0.05 );
182
- color : #fff ;
199
+ flex-direction : column ;
183
200
}
184
201
185
- .item-developer-info {
202
+ .card-title-row {
186
203
display : flex ;
187
204
align-items : center ;
188
- gap : 0.4rem ;
189
- flex-grow : 1 ;
190
- min-width : 0 ;
191
- }
192
-
193
- .developer-avatar {
194
- width : 20px ;
195
- height : 20px ;
196
- border-radius : 50% ;
197
- object-fit : cover ;
198
- cursor : default ;
199
- pointer-events : none ;
205
+ gap : 0.5rem ;
206
+ margin-bottom : 0.5rem ;
207
+ flex-wrap : nowrap ;
208
+ width : 100% ;
200
209
}
201
210
202
- .developer-name {
203
- font-size : 0.85rem ;
204
- color : var (--vp-c-text-2 );
211
+ .card-title {
212
+ font-size : 1.25rem ;
213
+ font-weight : 600 ;
214
+ color : var (--vp-c-text-1 );
215
+ margin : 0 ;
216
+ line-height : 1.4 ;
217
+ flex-grow : 1 ;
205
218
overflow : hidden ;
206
219
text-overflow : ellipsis ;
207
220
white-space : nowrap ;
208
221
}
209
222
223
+ .card-title-link {
224
+ flex-shrink : 0 ;
225
+ display : flex ;
226
+ font-size : 1.5rem
227
+ }
228
+
210
229
.no-external-icon ::after {
211
230
content : none !important ;
212
231
}
@@ -218,31 +237,14 @@ const handleCardClick = (item: PluginItem) => {
218
237
border-radius : 4px ;
219
238
border : 1px solid var (--vp-c-border );
220
239
background : rgba (255 , 255 , 255 , 0.1 );
221
- flex-shrink : 0 ;
222
- margin-left : 0.5rem ;
223
240
white-space : nowrap ;
224
241
font-weight : 500 ;
225
242
}
226
243
227
- .card-content {
228
- padding : 0.75rem ;
229
- flex-grow : 1 ;
230
- display : flex ;
231
- flex-direction : column ;
232
- }
233
-
234
- .card-title {
235
- font-size : 1.25rem ;
236
- font-weight : 600 ;
237
- color : var (--vp-c-text-1 );
238
- margin : 0 0 0.5rem 0 ;
239
- line-height : 1.4 ;
240
- }
241
-
242
244
.card-description {
243
245
color : var (--vp-c-text-2 );
244
246
font-size : 0.75rem ;
245
- line-height : 1.5 ;
247
+ line-height : 1.8 ;
246
248
margin : 0 0 1rem ;
247
249
flex-grow : 1 ;
248
250
}
@@ -280,6 +282,28 @@ const handleCardClick = (item: PluginItem) => {
280
282
text-transform : uppercase ;
281
283
}
282
284
285
+ .search-container {
286
+ position : relative ;
287
+ max-width : 30% ;
288
+ margin : 0 auto 2rem ;
289
+ }
290
+
291
+ .search-input {
292
+ width : 100% ;
293
+ padding : 0.5rem 2rem 0.5rem 0.75rem ;
294
+ border : 1px solid var (--vp-c-border );
295
+ border-radius : 4px ;
296
+ font-size : 0.875rem ;
297
+ background : var (--vp-c-bg );
298
+ color : var (--vp-c-text-1 );
299
+ font-family : ' Segoe UI' , Tahoma , Geneva, Verdana , sans-serif ;
300
+ }
301
+
302
+ .search-input :focus {
303
+ outline : none ;
304
+ border-color : var (--vp-c-brand );
305
+ }
306
+
283
307
@media (min-width : 768px ) {
284
308
.plugin-card-container {
285
309
grid-template-columns : repeat (2 , 1fr );
0 commit comments