Skip to content

Commit fc04d6a

Browse files
committed
feat(docs): 添加自定义搜索组件
- 新增 Search 组件用于文档搜索 - 实现了关键词搜索、历史记录、搜索引擎切换等功能 - 集成了 radix-vue 组件库 - 优化了搜索界面样式
1 parent 92962e3 commit fc04d6a

File tree

6 files changed

+521
-1
lines changed

6 files changed

+521
-1
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<script setup lang="ts">
2+
import { SwitchRoot, SwitchThumb } from 'radix-vue';
3+
import { useSearch } from './useSearch';
4+
5+
const {
6+
keyword,
7+
placeholder,
8+
keywordHistory,
9+
useHistory,
10+
engineList,
11+
engine,
12+
useLastEngine,
13+
useAfterHalfYear,
14+
search,
15+
saveUseLastEngine,
16+
} = useSearch();
17+
</script>
18+
19+
<template>
20+
<div class="search">
21+
<form @submit.prevent="search()">
22+
<input
23+
class="search-input"
24+
type="text"
25+
:placeholder="placeholder"
26+
v-model="keyword"
27+
/>
28+
</form>
29+
30+
<section class="search-tool">
31+
<div class="search-history">
32+
<span
33+
v-if="keywordHistory"
34+
class="search-history-item"
35+
:title="keywordHistory"
36+
@click="useHistory()"
37+
>{{ keywordHistory }}</span
38+
>
39+
<span v-else>--</span>
40+
</div>
41+
42+
<div class="tool flex-none flex items-center space-x-4">
43+
<div class="search-tool-item">
44+
<label for="search-switch-use-last-engine">上次使用</label>
45+
<SwitchRoot
46+
id="search-switch-use-last-engine"
47+
class="search-switch"
48+
v-model:checked="useLastEngine"
49+
@update:checked="saveUseLastEngine()"
50+
>
51+
<SwitchThumb class="search-switch-thumb" />
52+
</SwitchRoot>
53+
</div>
54+
55+
<div class="search-tool-item">
56+
<label for="search-switch-use-this-year">近半年</label>
57+
<SwitchRoot
58+
id="search-switch-use-this-year"
59+
class="search-switch"
60+
v-model:checked="useAfterHalfYear"
61+
>
62+
<SwitchThumb class="search-switch-thumb" />
63+
</SwitchRoot>
64+
</div>
65+
</div>
66+
</section>
67+
68+
<section>
69+
<div class="search-engine-list">
70+
<div
71+
v-for="item in engineList"
72+
:class="{
73+
engine: true,
74+
active: item.title === engine.title,
75+
}"
76+
@click="search(item)"
77+
>
78+
{{ item.title }}
79+
</div>
80+
</div>
81+
</section>
82+
</div>
83+
</template>
84+
85+
<style>
86+
.search > *:not(:last-child) {
87+
margin-bottom: 8px;
88+
}
89+
90+
.search .search-input {
91+
display: block;
92+
width: 100%;
93+
padding: 8px 16px;
94+
font-size: 16px;
95+
border: 1px solid var(--vp-c-border);
96+
border-radius: 8px;
97+
}
98+
99+
.search .search-input:hover,
100+
.search .search-input:focus {
101+
border: 1px solid var(--vp-c-brand-1);
102+
}
103+
104+
.search .search-tool {
105+
display: flex;
106+
justify-content: space-between;
107+
gap: 8px;
108+
line-height: 28px;
109+
font-size: 12px;
110+
color: var(--vp-c-text-2);
111+
}
112+
113+
.search .search-history {
114+
white-space: nowrap;
115+
text-overflow: ellipsis;
116+
overflow: hidden;
117+
}
118+
119+
.search .search-history-item:hover {
120+
color: var(--vp-c-brand-1);
121+
cursor: pointer;
122+
}
123+
124+
.search .search-tool-item {
125+
flex: none;
126+
display: flex;
127+
align-items: center;
128+
gap: 8px;
129+
}
130+
131+
.search .search-switch {
132+
display: block;
133+
width: 40px;
134+
height: 22px;
135+
position: relative;
136+
border: 1px solid var(--vp-input-border-color);
137+
border-radius: 11px;
138+
background-color: var(--vp-input-switch-bg-color);
139+
transition: border-color 0.25s !important;
140+
}
141+
142+
.search .search-switch:hover {
143+
border-color: var(--vp-c-brand-1);
144+
}
145+
146+
.search .search-switch[data-state='checked'] {
147+
border-color: var(--vp-c-brand-1);
148+
background-color: var(--vp-c-brand-1);
149+
}
150+
151+
.search .search-switch-thumb {
152+
position: absolute;
153+
top: 1px;
154+
left: 1px;
155+
display: block;
156+
width: 18px;
157+
height: 18px;
158+
border-radius: 9px;
159+
background-color: var(--vp-c-neutral-inverse);
160+
box-shadow: var(--vp-shadow-1);
161+
transition: transform 0.25s !important;
162+
}
163+
164+
.search .search-switch-thumb[data-state='checked'] {
165+
transform: translateX(18px);
166+
}
167+
168+
.search .search-engine-list {
169+
display: grid;
170+
grid-template-columns: repeat(3, 1fr);
171+
gap: 8px;
172+
padding-left: 0;
173+
}
174+
175+
.search .engine {
176+
line-height: 24px;
177+
color: var(--vp-c-text-2);
178+
list-style-type: none;
179+
cursor: pointer;
180+
}
181+
182+
.search .engine:hover,
183+
.search .engine.active {
184+
color: var(--vp-c-brand-1);
185+
}
186+
</style>
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { ref, shallowRef } from 'vue';
2+
3+
interface SearchEngineItem {
4+
title: string;
5+
url: string;
6+
}
7+
8+
export const useSearch = () => {
9+
const keyword = ref('');
10+
const placeholder = ref('搜点什么呢');
11+
const keywordHistory = ref('');
12+
13+
const engineList: SearchEngineItem[] = [
14+
{ title: '秘塔', url: 'https://metaso.cn/?q=%s' },
15+
{ title: 'Felo', url: 'https://felo.ai/search?q=%s' },
16+
{ title: '必应', url: 'https://www.bing.com/search?q=%s' },
17+
{
18+
title: 'V2EX',
19+
url: `https://www.google.com/search?q=%s${encodeURIComponent(' site:v2ex.com')}`,
20+
},
21+
{ title: 'Luxirty', url: 'https://search.luxirty.com/search?q=%s' },
22+
{ title: 'Google', url: 'https://www.google.com/search?q=%s' },
23+
{ title: 'DuckDuckGo', url: 'https://duckduckgo.com/?q=%s' },
24+
{
25+
title: 'GitHub',
26+
url: 'https://github.com/search?q=%s&type=repositories',
27+
},
28+
{ title: 'MDN', url: 'https://developer.mozilla.org/en-US/search?q=%s' },
29+
{ title: 'CanIUse', url: 'https://caniuse.com/?search=%s' },
30+
{ title: 'LONGMAN', url: 'https://www.ldoceonline.com/dictionary/%s' },
31+
{
32+
title: 'DeepL',
33+
url: 'https://www.deepl.com/en/translator#en/zh-hans/%s',
34+
},
35+
{
36+
title: 'GoogleTranslate',
37+
url: 'https://translate.google.com/?sl=auto&tl=zh-CN&text=%s&op=translate',
38+
},
39+
{
40+
title: '百度翻译',
41+
url: 'https://fanyi.baidu.com/mtpe-individual/multimodal?query=%s',
42+
},
43+
{ title: '欧路', url: 'https://dict.eudic.net/dicts/en/%s' },
44+
{ title: '掘金', url: 'https://juejin.cn/search?query=%s' },
45+
{ title: '知乎', url: 'https://www.zhihu.com/search?type=content&q=%s' },
46+
{ title: '哔哩哔哩', url: 'https://search.bilibili.com/all?keyword=%s' },
47+
{
48+
title: 'YouTube',
49+
url: 'https://www.youtube.com/results?search_query=%s',
50+
},
51+
];
52+
53+
const engine = shallowRef(engineList[0]);
54+
const useLastEngine = ref(false);
55+
const ENGINE_KEY = 'lb_search_engine';
56+
const SEARCH_HISTORY_KEY = 'lb_search_history';
57+
const USE_LAST_ENGINE_KEY = 'lb_search_engine_switch';
58+
59+
const useAfterHalfYear = ref(false);
60+
61+
const updateEngine = (newEngine: SearchEngineItem) => {
62+
if (!useLastEngine.value) {
63+
return;
64+
}
65+
engine.value = newEngine;
66+
localStorage.setItem(ENGINE_KEY, newEngine.title);
67+
};
68+
69+
const loadEngine = () => {
70+
if (!useLastEngine.value) {
71+
return;
72+
}
73+
const engineTitle = localStorage.getItem(ENGINE_KEY) ?? '';
74+
const lastEngine = engineList.find((item) => item.title === engineTitle);
75+
if (lastEngine) {
76+
engine.value = lastEngine;
77+
}
78+
};
79+
80+
const saveHistory = () => {
81+
const value = keyword.value.trim();
82+
if (value) {
83+
keywordHistory.value = value;
84+
localStorage.setItem(SEARCH_HISTORY_KEY, value);
85+
}
86+
};
87+
88+
const loadHistory = () => {
89+
const value = localStorage.getItem(SEARCH_HISTORY_KEY);
90+
if (value) {
91+
keywordHistory.value = value;
92+
}
93+
};
94+
95+
const useHistory = () => {
96+
keyword.value = keywordHistory.value;
97+
};
98+
99+
const saveUseLastEngine = () => {
100+
localStorage.setItem(USE_LAST_ENGINE_KEY, String(useLastEngine.value));
101+
engine.value = engineList[0];
102+
};
103+
104+
const loadUseLastEngine = () => {
105+
const isLastOff = localStorage.getItem(USE_LAST_ENGINE_KEY) === 'false';
106+
useLastEngine.value = !isLastOff;
107+
};
108+
109+
const search = (targetEngine = engine.value) => {
110+
updateEngine(targetEngine);
111+
112+
if (!keyword.value) {
113+
placeholder.value = '输入关键词后才能搜索~';
114+
return;
115+
}
116+
117+
let text = keyword.value;
118+
if (useAfterHalfYear.value) {
119+
const date = new Date();
120+
date.setMonth(date.getMonth() - 6);
121+
const y = date.getFullYear();
122+
const m = date.getMonth();
123+
const afterHalfYear = `after:${y}-${m + 1}-01`;
124+
text = `${text} ${afterHalfYear}`;
125+
}
126+
127+
saveHistory();
128+
const url = targetEngine.url.replace(
129+
'%s',
130+
encodeURIComponent(text),
131+
);
132+
const w = window.open(url, '_self', 'noopener,noreferrer');
133+
if (w) {
134+
w.opener = null;
135+
}
136+
};
137+
138+
loadUseLastEngine();
139+
loadEngine();
140+
loadHistory();
141+
142+
return {
143+
keyword,
144+
placeholder,
145+
keywordHistory,
146+
useHistory,
147+
engineList,
148+
engine,
149+
search,
150+
useLastEngine,
151+
useAfterHalfYear,
152+
saveUseLastEngine,
153+
};
154+
};

docs/.vitepress/theme/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Teek from "vitepress-theme-teek";
22
import TeekLayoutProvider from "./components/TeekLayoutProvider.vue";
3+
import Search from "./components/Search.vue";
34

45
// Teek 在线主题包引用(需安装 Teek 在线版本)
56
import "vitepress-theme-teek/index.css";
@@ -21,4 +22,8 @@ import "./styles/iframe.scss";
2122
export default {
2223
extends: Teek,
2324
Layout: TeekLayoutProvider,
25+
enhanceApp({app}) {
26+
// 注册全局组件
27+
app.component('Search' , Search)
28+
}
2429
};

docs/index.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ features:
3131
details: 学完之后,看山还是山,进入返璞归真之境
3232
---
3333

34-
![](/ghimgs/other/mindmap.webp)
34+
<br>
35+
<br>
36+
<Search />
3537

38+
![](/ghimgs/other/mindmap.webp)
3639

3740

3841
<style>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"vitepress": "^1.6.3"
1717
},
1818
"dependencies": {
19+
"radix-vue": "^1.9.17",
1920
"vitepress-theme-teek": "^1.3.5",
2021
"vue": "^3.5.16"
2122
}

0 commit comments

Comments
 (0)