@coffic/cosy-ui 0.2.3 → 0.3.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -6
- package/dist/app.css +1 -1
- package/dist/components/display/CodeExample.astro +39 -206
- package/dist/components/layouts/DocumentationLayout.astro +28 -53
- package/dist/components/layouts/Footer.astro +25 -16
- package/dist/components/layouts/Header.astro +53 -10
- package/dist/components/layouts/Sidebar.astro +119 -0
- package/dist/components/navigation/TableOfContents.astro +64 -61
- package/dist/components/typography/Article.astro +19 -3
- package/dist/utils/path.ts +15 -0
- package/package.json +11 -4
@@ -28,7 +28,8 @@
|
|
28
28
|
*/
|
29
29
|
|
30
30
|
// 导入图标
|
31
|
-
import { ClipboardIcon, CheckIcon } from '../../index'
|
31
|
+
import { ClipboardIcon, CheckIcon } from '../../index'
|
32
|
+
import "../../app.css"
|
32
33
|
|
33
34
|
interface Props {
|
34
35
|
title?: string;
|
@@ -39,138 +40,40 @@ interface Props {
|
|
39
40
|
const { title, description, code } = Astro.props;
|
40
41
|
---
|
41
42
|
|
42
|
-
<div class="code-example">
|
43
|
-
{title && <h3 class="
|
44
|
-
{description && <p class="
|
45
|
-
|
46
|
-
<div class="
|
47
|
-
<
|
48
|
-
|
43
|
+
<div class="card bg-base-100 shadow-lg mb-8 rounded-none code-example">
|
44
|
+
{title && <h3 class="card-title p-4 border-b border-base-300">{title}</h3>}
|
45
|
+
{description && <p class="px-4 pt-4 text-base-content/70">{description}</p>}
|
46
|
+
|
47
|
+
<div class="flex justify-between items-center px-4 bg-base-200 rounded-none">
|
48
|
+
<div role="tablist" class="tabs tabs-box">
|
49
|
+
<button role="tab" class="tab tab-active" data-tab="preview">预览</button>
|
50
|
+
<button role="tab" class="tab" data-tab="code">代码</button>
|
51
|
+
</div>
|
52
|
+
<div class="flex items-center gap-2">
|
53
|
+
<button class="btn btn-ghost btn-sm gap-2" aria-label="复制代码">
|
54
|
+
<span class="copy-icon"><ClipboardIcon /></span>
|
55
|
+
<span class="check-icon hidden text-success"><CheckIcon /></span>
|
56
|
+
<span class="copy-text">复制代码</span>
|
57
|
+
</button>
|
58
|
+
</div>
|
49
59
|
</div>
|
50
60
|
|
51
|
-
<div class="
|
61
|
+
<div class="relative p-0">
|
52
62
|
<div class="code-example-panel active" data-panel="preview">
|
53
|
-
<div class="
|
63
|
+
<div class="p-6 bg-base-100">
|
54
64
|
<slot />
|
55
65
|
</div>
|
56
66
|
</div>
|
57
67
|
|
58
68
|
<div class="code-example-panel" data-panel="code">
|
59
|
-
<div class="
|
60
|
-
<
|
61
|
-
<button class="copy-button" aria-label="复制代码">
|
62
|
-
<span class="copy-icon"><ClipboardIcon /></span>
|
63
|
-
<span class="check-icon"><CheckIcon /></span>
|
64
|
-
<span class="copy-text">复制代码</span>
|
65
|
-
</button>
|
66
|
-
</div>
|
67
|
-
<pre><code class="language-astro">{code}</code></pre>
|
69
|
+
<div class="bg-base-300 px-6 py-2">
|
70
|
+
<pre class="overflow-x-auto"><code class="language-astro">{code}</code></pre>
|
68
71
|
</div>
|
69
72
|
</div>
|
70
73
|
</div>
|
71
74
|
</div>
|
72
75
|
|
73
76
|
<style>
|
74
|
-
/* 颜色变量 - 支持亮色和暗黑模式 */
|
75
|
-
:root {
|
76
|
-
/* 亮色模式变量 */
|
77
|
-
--code-example-border: #e2e8f0;
|
78
|
-
--code-example-bg: #ffffff;
|
79
|
-
--code-example-text: #333333;
|
80
|
-
--code-example-title-border: #e2e8f0;
|
81
|
-
--code-example-description-color: #64748b;
|
82
|
-
--code-example-tabs-bg: #f8fafc;
|
83
|
-
--code-example-tab-color: #64748b;
|
84
|
-
--code-example-tab-hover-color: #0f172a;
|
85
|
-
--code-example-tab-active-color: #3b82f6;
|
86
|
-
--code-example-tab-active-border: #3b82f6;
|
87
|
-
--code-example-preview-bg: #ffffff;
|
88
|
-
--code-example-code-bg: #1e293b;
|
89
|
-
--code-example-code-text: #e2e8f0;
|
90
|
-
--code-example-header-bg: #334155;
|
91
|
-
--code-example-copy-button-color: #e2e8f0;
|
92
|
-
--code-example-copy-button-hover-bg: rgba(255, 255, 255, 0.1);
|
93
|
-
--code-example-copy-success-color: #4ade80;
|
94
|
-
}
|
95
|
-
|
96
|
-
@media (prefers-color-scheme: dark) {
|
97
|
-
:root {
|
98
|
-
/* 暗黑模式变量 */
|
99
|
-
--code-example-border: #2d3748;
|
100
|
-
--code-example-bg: #1a202c;
|
101
|
-
--code-example-text: #e2e8f0;
|
102
|
-
--code-example-title-border: #2d3748;
|
103
|
-
--code-example-description-color: #a0aec0;
|
104
|
-
--code-example-tabs-bg: #171e2e;
|
105
|
-
--code-example-tab-color: #a0aec0;
|
106
|
-
--code-example-tab-hover-color: #e2e8f0;
|
107
|
-
--code-example-tab-active-color: #60a5fa;
|
108
|
-
--code-example-tab-active-border: #60a5fa;
|
109
|
-
--code-example-preview-bg: #1a202c;
|
110
|
-
--code-example-code-bg: #0f172a;
|
111
|
-
--code-example-code-text: #e2e8f0;
|
112
|
-
--code-example-header-bg: #1e293b;
|
113
|
-
--code-example-copy-button-color: #e2e8f0;
|
114
|
-
--code-example-copy-button-hover-bg: rgba(255, 255, 255, 0.1);
|
115
|
-
--code-example-copy-success-color: #4ade80;
|
116
|
-
}
|
117
|
-
}
|
118
|
-
|
119
|
-
.code-example {
|
120
|
-
margin-bottom: 2rem;
|
121
|
-
border: 1px solid var(--code-example-border);
|
122
|
-
border-radius: 0.5rem;
|
123
|
-
overflow: hidden;
|
124
|
-
background-color: var(--code-example-bg);
|
125
|
-
color: var(--code-example-text);
|
126
|
-
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
127
|
-
}
|
128
|
-
|
129
|
-
.code-example-title {
|
130
|
-
font-size: 1.25rem;
|
131
|
-
font-weight: bold;
|
132
|
-
margin: 0;
|
133
|
-
padding: 1rem;
|
134
|
-
border-bottom: 1px solid var(--code-example-title-border);
|
135
|
-
}
|
136
|
-
|
137
|
-
.code-example-description {
|
138
|
-
padding: 0 1rem;
|
139
|
-
color: var(--code-example-description-color);
|
140
|
-
margin-top: 0.5rem;
|
141
|
-
margin-bottom: 1rem;
|
142
|
-
}
|
143
|
-
|
144
|
-
.code-example-tabs {
|
145
|
-
display: flex;
|
146
|
-
border-bottom: 1px solid var(--code-example-title-border);
|
147
|
-
background-color: var(--code-example-tabs-bg);
|
148
|
-
}
|
149
|
-
|
150
|
-
.code-example-tab {
|
151
|
-
padding: 0.75rem 1.5rem;
|
152
|
-
background: transparent;
|
153
|
-
border: none;
|
154
|
-
border-bottom: 2px solid transparent;
|
155
|
-
cursor: pointer;
|
156
|
-
font-weight: 500;
|
157
|
-
color: var(--code-example-tab-color);
|
158
|
-
transition: all 0.2s ease;
|
159
|
-
}
|
160
|
-
|
161
|
-
.code-example-tab:hover {
|
162
|
-
color: var(--code-example-tab-hover-color);
|
163
|
-
}
|
164
|
-
|
165
|
-
.code-example-tab.active {
|
166
|
-
color: var(--code-example-tab-active-color);
|
167
|
-
border-bottom-color: var(--code-example-tab-active-border);
|
168
|
-
}
|
169
|
-
|
170
|
-
.code-example-content {
|
171
|
-
position: relative;
|
172
|
-
}
|
173
|
-
|
174
77
|
.code-example-panel {
|
175
78
|
display: none;
|
176
79
|
}
|
@@ -178,99 +81,23 @@ const { title, description, code } = Astro.props;
|
|
178
81
|
.code-example-panel.active {
|
179
82
|
display: block;
|
180
83
|
}
|
181
|
-
|
182
|
-
.code-example-preview {
|
183
|
-
padding: 1.5rem;
|
184
|
-
background-color: var(--code-example-preview-bg);
|
185
|
-
}
|
186
|
-
|
187
|
-
.code-example-code {
|
188
|
-
position: relative;
|
189
|
-
background-color: var(--code-example-code-bg);
|
190
|
-
color: var(--code-example-code-text);
|
191
|
-
overflow: auto;
|
192
|
-
}
|
193
|
-
|
194
|
-
.code-example-header {
|
195
|
-
display: flex;
|
196
|
-
justify-content: flex-end;
|
197
|
-
align-items: center;
|
198
|
-
padding: 0.5rem 1rem;
|
199
|
-
background-color: var(--code-example-header-bg);
|
200
|
-
}
|
201
|
-
|
202
|
-
.copy-button {
|
203
|
-
background: transparent;
|
204
|
-
border: none;
|
205
|
-
color: var(--code-example-copy-button-color);
|
206
|
-
cursor: pointer;
|
207
|
-
display: flex;
|
208
|
-
align-items: center;
|
209
|
-
gap: 0.5rem;
|
210
|
-
padding: 0.5rem 0.75rem;
|
211
|
-
border-radius: 0.25rem;
|
212
|
-
transition: background-color 0.2s;
|
213
|
-
font-size: 0.875rem;
|
214
|
-
}
|
215
|
-
|
216
|
-
.copy-button:hover {
|
217
|
-
background-color: var(--code-example-copy-button-hover-bg);
|
218
|
-
}
|
219
|
-
|
220
|
-
.copy-icon, .check-icon {
|
221
|
-
display: flex;
|
222
|
-
align-items: center;
|
223
|
-
justify-content: center;
|
224
|
-
width: 1rem;
|
225
|
-
height: 1rem;
|
226
|
-
}
|
227
|
-
|
228
|
-
.check-icon {
|
229
|
-
display: none;
|
230
|
-
color: var(--code-example-copy-success-color);
|
231
|
-
}
|
232
|
-
|
233
|
-
.copy-button.copied .copy-icon {
|
234
|
-
display: none;
|
235
|
-
}
|
236
|
-
|
237
|
-
.copy-button.copied .check-icon {
|
238
|
-
display: flex;
|
239
|
-
}
|
240
|
-
|
241
|
-
.copy-button.copied .copy-text {
|
242
|
-
color: var(--code-example-copy-success-color);
|
243
|
-
}
|
244
|
-
|
245
|
-
pre {
|
246
|
-
margin: 0;
|
247
|
-
padding: 1rem;
|
248
|
-
overflow-x: auto;
|
249
|
-
}
|
250
|
-
|
251
|
-
code {
|
252
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
253
|
-
white-space: pre;
|
254
|
-
font-size: 0.9rem;
|
255
|
-
line-height: 1.5;
|
256
|
-
}
|
257
84
|
</style>
|
258
85
|
|
259
86
|
<script>
|
260
87
|
function initializeCodeExample() {
|
261
88
|
// 标签切换功能
|
262
|
-
const tabs = document.querySelectorAll('.
|
89
|
+
const tabs = document.querySelectorAll('.tab');
|
263
90
|
|
264
91
|
tabs.forEach(tab => {
|
265
92
|
tab.addEventListener('click', () => {
|
266
93
|
// 获取当前标签组
|
267
|
-
const tabGroup = tab.closest('.
|
94
|
+
const tabGroup = tab.closest('.tabs');
|
268
95
|
if (!tabGroup) return;
|
269
96
|
|
270
|
-
const codeExample = tab.closest('.
|
97
|
+
const codeExample = tab.closest('.card');
|
271
98
|
if (!codeExample) return;
|
272
99
|
|
273
|
-
const contentContainer = codeExample.querySelector('.
|
100
|
+
const contentContainer = codeExample.querySelector('.relative');
|
274
101
|
if (!contentContainer) return;
|
275
102
|
|
276
103
|
// 获取目标面板
|
@@ -278,10 +105,10 @@ const { title, description, code } = Astro.props;
|
|
278
105
|
if (!targetTab) return;
|
279
106
|
|
280
107
|
// 更新标签状态
|
281
|
-
tabGroup.querySelectorAll('.
|
282
|
-
t.classList.remove('active');
|
108
|
+
tabGroup.querySelectorAll('.tab').forEach(t => {
|
109
|
+
t.classList.remove('tab-active');
|
283
110
|
});
|
284
|
-
tab.classList.add('active');
|
111
|
+
tab.classList.add('tab-active');
|
285
112
|
|
286
113
|
// 更新面板状态
|
287
114
|
contentContainer.querySelectorAll('.code-example-panel').forEach(panel => {
|
@@ -296,11 +123,11 @@ const { title, description, code } = Astro.props;
|
|
296
123
|
});
|
297
124
|
|
298
125
|
// 复制代码功能
|
299
|
-
const copyButtons = document.querySelectorAll('.
|
126
|
+
const copyButtons = document.querySelectorAll('.btn-ghost');
|
300
127
|
|
301
128
|
copyButtons.forEach(button => {
|
302
129
|
button.addEventListener('click', () => {
|
303
|
-
const codeBlock = button.closest('.
|
130
|
+
const codeBlock = button.closest('.bg-base-300');
|
304
131
|
if (!codeBlock) return;
|
305
132
|
|
306
133
|
const codeElement = codeBlock.querySelector('code');
|
@@ -311,11 +138,17 @@ const { title, description, code } = Astro.props;
|
|
311
138
|
// 复制到剪贴板
|
312
139
|
navigator.clipboard.writeText(code).then(() => {
|
313
140
|
// 显示复制成功状态
|
314
|
-
button.classList.add('
|
141
|
+
button.classList.add('btn-success');
|
142
|
+
button.querySelector('.copy-icon')?.classList.add('hidden');
|
143
|
+
button.querySelector('.check-icon')?.classList.remove('hidden');
|
144
|
+
button.querySelector('.copy-text')?.classList.add('text-success');
|
315
145
|
|
316
146
|
// 3秒后恢复原状
|
317
147
|
setTimeout(() => {
|
318
|
-
button.classList.remove('
|
148
|
+
button.classList.remove('btn-success');
|
149
|
+
button.querySelector('.copy-icon')?.classList.remove('hidden');
|
150
|
+
button.querySelector('.check-icon')?.classList.add('hidden');
|
151
|
+
button.querySelector('.copy-text')?.classList.remove('text-success');
|
319
152
|
}, 3000);
|
320
153
|
});
|
321
154
|
});
|
@@ -79,6 +79,7 @@ import Article from '../typography/Article.astro';
|
|
79
79
|
import Footer from './Footer.astro';
|
80
80
|
import Main from '../containers/Main.astro';
|
81
81
|
import Header from './Header.astro';
|
82
|
+
import Sidebar from './Sidebar.astro';
|
82
83
|
import DefaultLogo from '../../assets/logo.png';
|
83
84
|
|
84
85
|
export interface SidebarItem {
|
@@ -120,6 +121,12 @@ export interface Props {
|
|
120
121
|
*/
|
121
122
|
logo?: ImageMetadata;
|
122
123
|
|
124
|
+
/**
|
125
|
+
* Logo链接地址
|
126
|
+
* @default "/"
|
127
|
+
*/
|
128
|
+
logoHref?: string;
|
129
|
+
|
123
130
|
/**
|
124
131
|
* 侧边栏项目
|
125
132
|
*/
|
@@ -182,6 +189,12 @@ export interface Props {
|
|
182
189
|
* 当前语言
|
183
190
|
*/
|
184
191
|
currentLocale?: string;
|
192
|
+
|
193
|
+
/**
|
194
|
+
* 基础路径,用于处理网站部署在二级目录的情况
|
195
|
+
* @default ""
|
196
|
+
*/
|
197
|
+
basePath?: string;
|
185
198
|
|
186
199
|
/**
|
187
200
|
* 页脚相关配置
|
@@ -297,6 +310,11 @@ export interface Props {
|
|
297
310
|
* 技术栈链接
|
298
311
|
*/
|
299
312
|
footerTechStackLink?: string;
|
313
|
+
|
314
|
+
/**
|
315
|
+
* 首页链接
|
316
|
+
*/
|
317
|
+
footerHomeLink?: string;
|
300
318
|
}
|
301
319
|
|
302
320
|
const {
|
@@ -305,6 +323,7 @@ const {
|
|
305
323
|
keywords,
|
306
324
|
siteName = "文档中心",
|
307
325
|
logo = DefaultLogo,
|
326
|
+
logoHref = "/",
|
308
327
|
sidebarItems,
|
309
328
|
showTableOfContents = true,
|
310
329
|
showHeader = true,
|
@@ -312,10 +331,12 @@ const {
|
|
312
331
|
head,
|
313
332
|
class: className,
|
314
333
|
'class:list': classList,
|
315
|
-
debug =
|
334
|
+
debug = true,
|
316
335
|
navItems,
|
317
336
|
languages,
|
318
337
|
currentLocale,
|
338
|
+
basePath = "",
|
339
|
+
footerHomeLink,
|
319
340
|
footerSlogan,
|
320
341
|
footerCompany,
|
321
342
|
footerCopyright,
|
@@ -354,10 +375,12 @@ const currentPath = Astro.url.pathname;
|
|
354
375
|
{showHeader && (
|
355
376
|
<Header
|
356
377
|
logo={logo}
|
378
|
+
logoHref={logoHref}
|
357
379
|
navItems={navItems}
|
358
380
|
languages={languages}
|
359
381
|
currentLocale={currentLocale}
|
360
382
|
sticky={true}
|
383
|
+
basePath={basePath}
|
361
384
|
/>
|
362
385
|
)}
|
363
386
|
|
@@ -368,61 +391,13 @@ const currentPath = Astro.url.pathname;
|
|
368
391
|
)}
|
369
392
|
|
370
393
|
<div class="flex-1 flex">
|
371
|
-
<
|
372
|
-
<nav class="p-4 sticky top-16">
|
373
|
-
{sidebarItems.map((section: SidebarSection) => (
|
374
|
-
<div class="mb-6">
|
375
|
-
<h3 class="font-bold mb-2 text-base-content/70">{section.title}</h3>
|
376
|
-
<ul class="menu menu-sm">
|
377
|
-
{section.items.map((item: SidebarItem) => {
|
378
|
-
const isActive = currentPath === item.href;
|
379
|
-
return (
|
380
|
-
<li>
|
381
|
-
<a
|
382
|
-
href={item.href}
|
383
|
-
class:list={[
|
384
|
-
"hover:bg-base-200",
|
385
|
-
{ "active": isActive }
|
386
|
-
]}
|
387
|
-
>
|
388
|
-
{item.text}
|
389
|
-
</a>
|
390
|
-
{item.items && (
|
391
|
-
<ul class="menu menu-sm pl-4">
|
392
|
-
{item.items.map((subitem: SidebarItem) => {
|
393
|
-
const isSubActive = currentPath === subitem.href;
|
394
|
-
return (
|
395
|
-
<li>
|
396
|
-
<a
|
397
|
-
href={subitem.href}
|
398
|
-
class:list={[
|
399
|
-
"hover:bg-base-200",
|
400
|
-
{ "active": isSubActive }
|
401
|
-
]}
|
402
|
-
>
|
403
|
-
{subitem.text}
|
404
|
-
</a>
|
405
|
-
</li>
|
406
|
-
);
|
407
|
-
})}
|
408
|
-
</ul>
|
409
|
-
)}
|
410
|
-
</li>
|
411
|
-
);
|
412
|
-
})}
|
413
|
-
</ul>
|
414
|
-
</div>
|
415
|
-
))}
|
416
|
-
</nav>
|
417
|
-
</aside>
|
394
|
+
<Sidebar sidebarItems={sidebarItems} currentPath={currentPath} />
|
418
395
|
|
419
396
|
<Main
|
420
|
-
class="flex-1 py-8"
|
397
|
+
class="flex-1 py-8 min-h-screen"
|
421
398
|
>
|
422
399
|
<div class="container mx-auto px-4 flex gap-8">
|
423
|
-
<Article
|
424
|
-
class="prose max-w-3xl"
|
425
|
-
>
|
400
|
+
<Article >
|
426
401
|
<slot />
|
427
402
|
</Article>
|
428
403
|
|
@@ -442,7 +417,7 @@ const currentPath = Astro.url.pathname;
|
|
442
417
|
{showFooter && (
|
443
418
|
<Footer
|
444
419
|
siteName={siteName}
|
445
|
-
homeLink="/"
|
420
|
+
homeLink={footerHomeLink || "/"}
|
446
421
|
slogan={footerSlogan || "优雅、高效的组件库"}
|
447
422
|
company={footerCompany || siteName}
|
448
423
|
copyright={footerCopyright || "保留所有权利"}
|
@@ -178,7 +178,7 @@ const debugClasses = debug ? {
|
|
178
178
|
---
|
179
179
|
|
180
180
|
<footer class:list={["footer sm:footer-horizontal bg-base-200 text-base-content p-10", debugClasses.footer]}>
|
181
|
-
<div class:list={["flex container mx-auto
|
181
|
+
<div class:list={["flex flex-col gap-8 container mx-auto items-center w-full md:flex-row md:justify-between", debugClasses.section]}>
|
182
182
|
{/* 品牌区域 */}
|
183
183
|
<aside class:list={["max-w-xs", debugClasses.aside]}>
|
184
184
|
<a href={homeLink} class="flex items-center gap-2 mb-4">
|
@@ -215,13 +215,14 @@ const debugClasses = debug ? {
|
|
215
215
|
)}
|
216
216
|
</aside>
|
217
217
|
|
218
|
-
|
218
|
+
{/* 导航区域 */}
|
219
|
+
<div class:list={["flex flex-col gap-8 mx-auto max-w-xl text-center items-center w-full md:flex-row md:justify-between md:items-start", debugClasses.section]}>
|
219
220
|
{/* 产品导航 */}
|
220
221
|
{products.length > 0 && (
|
221
|
-
<nav class:list={["", debugClasses.nav]}>
|
222
|
+
<nav class:list={["h-full", debugClasses.nav]}>
|
222
223
|
<h6 class="footer-title">产品</h6>
|
223
224
|
{products.map((product) => (
|
224
|
-
<Link href={product.href} external={product.external} block animation='hover-lift'>
|
225
|
+
<Link href={product.href} external={product.external} size='sm' block animation='hover-lift'>
|
225
226
|
{product.name}
|
226
227
|
</Link>
|
227
228
|
))}
|
@@ -230,12 +231,12 @@ const debugClasses = debug ? {
|
|
230
231
|
|
231
232
|
{/* 关于导航 */}
|
232
233
|
{(aboutLink || contactLink || teamLink || careersLink) && (
|
233
|
-
<nav class:list={["", debugClasses.nav]}>
|
234
|
-
<h6 class="footer-title"
|
235
|
-
{aboutLink && <Link href={aboutLink} block animation='hover-lift'>关于我们</Link>}
|
236
|
-
{teamLink && <Link href={teamLink} block animation='hover-lift'>团队介绍</Link>}
|
237
|
-
{careersLink && <Link href={careersLink} block animation='hover-lift'>加入我们</Link>}
|
238
|
-
{contactLink && <Link href={contactLink} block animation='hover-lift'>联系我们</Link>}
|
234
|
+
<nav class:list={["h-full", debugClasses.nav]}>
|
235
|
+
<h6 class="footer-title">关于</h6>
|
236
|
+
{aboutLink && <Link href={aboutLink} block animation='hover-lift' size='sm'>关于我们</Link>}
|
237
|
+
{teamLink && <Link href={teamLink} block animation='hover-lift' size='sm'>团队介绍</Link>}
|
238
|
+
{careersLink && <Link href={careersLink} block animation='hover-lift' size='sm'>加入我们</Link>}
|
239
|
+
{contactLink && <Link href={contactLink} block animation='hover-lift' size='sm'>联系我们</Link>}
|
239
240
|
</nav>
|
240
241
|
)}
|
241
242
|
|
@@ -243,10 +244,11 @@ const debugClasses = debug ? {
|
|
243
244
|
{(newsLink || historyLink || partnersLink || blogLink || faqLink || mediaLink || techStackLink) && (
|
244
245
|
<nav class:list={["", debugClasses.nav]}>
|
245
246
|
<h6 class="footer-title">资源</h6>
|
246
|
-
{newsLink && <Link href={newsLink} block animation='hover-lift'>新闻动态</Link>}
|
247
|
-
{blogLink && <Link href={blogLink} block animation='hover-lift'>技术博客</Link>}
|
248
|
-
{faqLink && <Link href={faqLink} block animation='hover-lift'>常见问题</Link>}
|
249
|
-
{
|
247
|
+
{newsLink && <Link href={newsLink} block animation='hover-lift' size='sm'>新闻动态</Link>}
|
248
|
+
{blogLink && <Link href={blogLink} block animation='hover-lift' size='sm'>技术博客</Link>}
|
249
|
+
{faqLink && <Link href={faqLink} block animation='hover-lift' size='sm'>常见问题</Link>}
|
250
|
+
{historyLink && <Link href={historyLink} block animation='hover-lift' size='sm'>发展历程</Link>}
|
251
|
+
{techStackLink && <Link href={techStackLink} block animation='hover-lift' size='sm'>技术栈</Link>}
|
250
252
|
</nav>
|
251
253
|
)}
|
252
254
|
|
@@ -254,8 +256,8 @@ const debugClasses = debug ? {
|
|
254
256
|
{(termsLink || privacyLink) && (
|
255
257
|
<nav class:list={["", debugClasses.nav]}>
|
256
258
|
<h6 class="footer-title">法律</h6>
|
257
|
-
{termsLink && <Link href={termsLink} block animation='hover-lift'>服务条款</Link>}
|
258
|
-
{privacyLink && <Link href={privacyLink} block animation='hover-lift'>隐私政策</Link>}
|
259
|
+
{termsLink && <Link href={termsLink} block animation='hover-lift' size='sm'>服务条款</Link>}
|
260
|
+
{privacyLink && <Link href={privacyLink} block animation='hover-lift' size='sm'>隐私政策</Link>}
|
259
261
|
</nav>
|
260
262
|
)}
|
261
263
|
</div>
|
@@ -282,3 +284,10 @@ const debugClasses = debug ? {
|
|
282
284
|
)}
|
283
285
|
</aside>
|
284
286
|
</div>
|
287
|
+
|
288
|
+
<style scoped>
|
289
|
+
@reference '../../app.css';
|
290
|
+
nav {
|
291
|
+
@apply flex flex-col gap-3;
|
292
|
+
}
|
293
|
+
</style>
|
@@ -15,6 +15,11 @@ interface ImageMetadata {
|
|
15
15
|
|
16
16
|
interface Props {
|
17
17
|
logo: ImageMetadata;
|
18
|
+
/**
|
19
|
+
* Logo 链接地址
|
20
|
+
* @default "/"
|
21
|
+
*/
|
22
|
+
logoHref?: string;
|
18
23
|
languages?: Array<{ code: string; name: string }>;
|
19
24
|
currentLocale?: string;
|
20
25
|
navItems?: Array<{
|
@@ -32,19 +37,47 @@ interface Props {
|
|
32
37
|
* @default true
|
33
38
|
*/
|
34
39
|
sticky?: boolean;
|
40
|
+
/**
|
41
|
+
* 基础路径,用于处理网站部署在二级目录的情况
|
42
|
+
* @default ""
|
43
|
+
*/
|
44
|
+
basePath?: string;
|
35
45
|
}
|
36
46
|
|
37
47
|
const {
|
38
48
|
logo,
|
49
|
+
logoHref = "/",
|
39
50
|
navItems = [],
|
40
51
|
sticky = true,
|
52
|
+
languages = [
|
53
|
+
{ code: 'zh-cn', name: '中文' },
|
54
|
+
{ code: 'en', name: 'English' }
|
55
|
+
],
|
56
|
+
currentLocale = 'zh-cn',
|
57
|
+
basePath = ''
|
41
58
|
} = Astro.props;
|
42
59
|
|
43
60
|
type NavItem = { href: string; label: string; match: (path: string) => boolean };
|
61
|
+
|
62
|
+
// 获取当前路径
|
63
|
+
const currentPath = Astro.url.pathname;
|
64
|
+
|
65
|
+
// 处理基础路径
|
66
|
+
const basePathPattern = basePath ? new RegExp(`^${basePath}`) : null;
|
67
|
+
const pathWithoutBase = basePathPattern ? currentPath.replace(basePathPattern, '') : currentPath;
|
68
|
+
|
69
|
+
// 提取路径部分,排除语言代码
|
70
|
+
const pathWithoutLocale = pathWithoutBase.replace(/^\/(zh-cn|en)/, '');
|
71
|
+
|
72
|
+
// 生成语言切换链接
|
73
|
+
function getLanguageUrl(langCode: string) {
|
74
|
+
// 如果有基础路径,需要加上
|
75
|
+
return `${basePath}/${langCode}${pathWithoutLocale}`;
|
76
|
+
}
|
44
77
|
---
|
45
78
|
|
46
79
|
<header class:list={[
|
47
|
-
"navbar bg-accent/
|
80
|
+
"navbar bg-accent/30 backdrop-blur border-base-200 z-50 w-full",
|
48
81
|
{ "fixed top-0": sticky }
|
49
82
|
]}>
|
50
83
|
<div class="navbar-start">
|
@@ -66,7 +99,7 @@ type NavItem = { href: string; label: string; match: (path: string) => boolean }
|
|
66
99
|
</ul>
|
67
100
|
</div>
|
68
101
|
|
69
|
-
<Link href=
|
102
|
+
<Link href={logoHref} class="btn btn-ghost">
|
70
103
|
<Image src={logo} alt="logo" class="w-10 h-10" />
|
71
104
|
</Link>
|
72
105
|
</div>
|
@@ -85,14 +118,24 @@ type NavItem = { href: string; label: string; match: (path: string) => boolean }
|
|
85
118
|
</div>
|
86
119
|
|
87
120
|
<div class="navbar-end">
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
121
|
+
<!-- 语言切换按钮 -->
|
122
|
+
<div class="dropdown dropdown-end">
|
123
|
+
<div tabindex="0" role="button" class="btn btn-ghost btn-sm">
|
124
|
+
<span class="mr-1">{currentLocale === 'zh-cn' ? '中文' : 'English'}</span>
|
125
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
126
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
127
|
+
</svg>
|
128
|
+
</div>
|
129
|
+
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-32">
|
130
|
+
{languages.map((lang) => (
|
131
|
+
<li class={currentLocale === lang.code ? "disabled" : ""}>
|
132
|
+
<a href={getLanguageUrl(lang.code)} class={currentLocale === lang.code ? "active" : ""}>
|
133
|
+
{lang.name}
|
134
|
+
</a>
|
135
|
+
</li>
|
136
|
+
))}
|
137
|
+
</ul>
|
138
|
+
</div>
|
96
139
|
</div>
|
97
140
|
</header>
|
98
141
|
|