Skip to content

Commit be5eb3d

Browse files
committed
feat(website): 添加下载条和图像轮播组件,优化首页布局
1 parent 623c3d7 commit be5eb3d

17 files changed

Lines changed: 406 additions & 53 deletions
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<script setup lang="ts">
2+
import { ref, computed, onMounted } from 'vue';
3+
import { useData } from 'vitepress';
4+
import { ArrowRight } from 'lucide-vue-next';
5+
6+
declare const __APP_VERSION__: string;
7+
const VERSION = __APP_VERSION__;
8+
9+
const { lang } = useData();
10+
const isZh = computed(() => lang.value === 'zh-CN');
11+
12+
const detectedOS = ref<'macos' | 'windows' | 'linux'>('macos');
13+
14+
onMounted(() => {
15+
const ua = navigator.userAgent.toLowerCase();
16+
if (ua.includes('win')) {
17+
detectedOS.value = 'windows';
18+
} else if (ua.includes('linux')) {
19+
detectedOS.value = 'linux';
20+
} else {
21+
detectedOS.value = 'macos';
22+
}
23+
});
24+
25+
const osLabel = computed(() => {
26+
const labels: Record<string, { en: string; zh: string }> = {
27+
macos: { en: 'for macOS', zh: 'macOS 版' },
28+
windows: { en: 'for Windows', zh: 'Windows 版' },
29+
linux: { en: 'for Linux', zh: 'Linux 版' },
30+
};
31+
const l = labels[detectedOS.value];
32+
return isZh.value ? l.zh : l.en;
33+
});
34+
</script>
35+
36+
<template>
37+
<div class="download-bar">
38+
<a href="/download" class="dl-btn dl-primary">
39+
<!-- OS icon -->
40+
<svg v-if="detectedOS === 'macos'" class="os-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
41+
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/>
42+
</svg>
43+
<svg v-else-if="detectedOS === 'windows'" class="os-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
44+
<path d="M3 12V6.5l8-1.1V12H3zm10 0V5.2l8-1.2V12h-8zM3 13h8v6.6l-8-1.1V13zm10 0h8v6l-8 1.2V13z"/>
45+
</svg>
46+
<svg v-else class="os-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
47+
<path d="M12.504 0c-.155 0-.311.003-.465.01a10.012 10.012 0 00-3.044.66c-.904.348-1.71.87-2.244 1.658C6.2 3.2 5.88 4.22 5.88 5.5c0 .78.116 1.49.346 2.13a6.7 6.7 0 00.96 1.73c.386.48.8.9 1.25 1.25.45.35.9.62 1.35.82.45.2.84.33 1.17.39.33.06.55.09.67.09h.02c.12 0 .34-.03.67-.09.33-.06.72-.19 1.17-.39.45-.2.9-.47 1.35-.82.45-.35.864-.77 1.25-1.25.386-.48.71-1.05.96-1.73.23-.64.346-1.35.346-2.13 0-1.28-.32-2.3-.872-3.172-.534-.788-1.34-1.31-2.244-1.658A10.012 10.012 0 0012.969.01C12.813.003 12.66 0 12.504 0zm-4.39 13.12c-.71 0-1.4.13-2.03.4-.62.27-1.16.65-1.61 1.14-.45.49-.8 1.08-1.04 1.76-.24.68-.37 1.43-.37 2.24 0 .6.06 1.15.17 1.63.12.48.29.9.52 1.25.23.35.52.63.87.84.35.21.76.31 1.23.31.35 0 .68-.06 1-.17.32-.12.65-.29 1-.52.35-.23.73-.52 1.14-.87.42-.35.89-.77 1.43-1.25.53.48 1.01.9 1.43 1.25.41.35.79.64 1.14.87.35.23.68.4 1 .52.32.11.65.17 1 .17.47 0 .88-.1 1.23-.31.35-.21.64-.49.87-.84.23-.35.4-.77.52-1.25.11-.48.17-1.03.17-1.63 0-.81-.13-1.56-.37-2.24-.24-.68-.59-1.27-1.04-1.76-.45-.49-.99-.87-1.61-1.14-.63-.27-1.32-.4-2.03-.4H12.5h-.01-4.385z"/>
48+
</svg>
49+
<span>{{ isZh ? '下载' : 'Download' }} v{{ VERSION }}</span>
50+
</a>
51+
<a href="/features" class="dl-btn dl-docs">
52+
<span>{{ isZh ? '文档' : 'Docs' }}</span>
53+
<ArrowRight :size="18" />
54+
</a>
55+
</div>
56+
</template>
57+
58+
<style scoped>
59+
.download-bar {
60+
display: flex;
61+
align-items: center;
62+
justify-content: center;
63+
gap: 20px;
64+
padding: 32px 0 8px;
65+
flex-wrap: wrap;
66+
}
67+
68+
.dl-btn {
69+
display: inline-flex;
70+
align-items: center;
71+
justify-content: center;
72+
gap: 10px;
73+
padding: 0 36px;
74+
height: 56px;
75+
border-radius: 28px;
76+
font-size: 17px;
77+
font-weight: 600;
78+
text-decoration: none;
79+
cursor: pointer;
80+
transition: all 0.25s ease;
81+
white-space: nowrap;
82+
}
83+
84+
.dl-primary {
85+
background: var(--vp-c-bg);
86+
color: var(--vp-c-text-1);
87+
border: 2px solid var(--vp-c-divider);
88+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
89+
}
90+
.dl-primary:hover {
91+
border-color: var(--vp-c-brand-1);
92+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
93+
transform: translateY(-1px);
94+
}
95+
96+
.dark .dl-primary {
97+
background: var(--vp-c-bg-soft);
98+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
99+
}
100+
101+
.dl-docs {
102+
color: var(--vp-c-text-1);
103+
border: 2px solid var(--vp-c-text-3);
104+
background: transparent;
105+
}
106+
.dl-docs:hover {
107+
border-color: var(--vp-c-brand-1);
108+
color: var(--vp-c-brand-1);
109+
transform: translateY(-1px);
110+
}
111+
112+
.os-icon {
113+
flex-shrink: 0;
114+
}
115+
116+
@media (max-width: 640px) {
117+
.dl-btn {
118+
padding: 0 28px;
119+
height: 50px;
120+
font-size: 15px;
121+
border-radius: 25px;
122+
gap: 8px;
123+
}
124+
.download-bar {
125+
gap: 14px;
126+
flex-direction: column;
127+
align-items: center;
128+
}
129+
}
130+
131+
@media (max-width: 480px) {
132+
.dl-btn {
133+
padding: 0 24px;
134+
height: 48px;
135+
font-size: 15px;
136+
border-radius: 24px;
137+
gap: 8px;
138+
width: 100%;
139+
}
140+
.download-bar {
141+
gap: 12px;
142+
flex-direction: column;
143+
align-items: stretch;
144+
padding: 24px 16px 8px;
145+
}
146+
}
147+
</style>
Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,30 @@
11
<script setup lang="ts">
22
import DefaultTheme from 'vitepress/theme';
33
import { useData } from 'vitepress';
4-
import DownloadHero from './DownloadHero.vue';
4+
import DownloadBar from './DownloadBar.vue';
5+
import ImageCarousel from './ImageCarousel.vue';
56
67
const { Layout } = DefaultTheme;
78
const { frontmatter } = useData();
89
</script>
910

1011
<template>
1112
<Layout>
13+
<template #home-hero-info-after>
14+
<div v-if="frontmatter?.layout === 'home'" class="hero-actions-custom">
15+
<DownloadBar />
16+
</div>
17+
</template>
1218
<template #home-features-before>
13-
<div v-if="frontmatter?.layout === 'home'" class="home-download-section">
14-
<DownloadHero />
19+
<div v-if="frontmatter?.layout === 'home'">
20+
<ImageCarousel />
1521
</div>
1622
</template>
1723
</Layout>
1824
</template>
1925

2026
<style scoped>
21-
.home-download-section {
22-
max-width: 1152px;
23-
margin: 0 auto;
24-
padding: 0 24px;
25-
}
26-
27-
@media (min-width: 640px) {
28-
.home-download-section {
29-
padding: 0 48px;
30-
}
31-
}
32-
33-
@media (min-width: 960px) {
34-
.home-download-section {
35-
padding: 0 64px;
36-
}
27+
.hero-actions-custom {
28+
width: 100%;
3729
}
3830
</style>
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<script setup lang="ts">
2+
import { Swiper, SwiperSlide } from 'swiper/vue';
3+
import { Autoplay, Pagination, Navigation } from 'swiper/modules';
4+
import { ChevronLeft, ChevronRight } from 'lucide-vue-next';
5+
6+
import 'swiper/css';
7+
import 'swiper/css/pagination';
8+
import 'swiper/css/navigation';
9+
10+
const modules = [Autoplay, Pagination, Navigation];
11+
12+
const screenshots = [
13+
'/screenshots/s1-0412.png',
14+
'/screenshots/s2-0412.png',
15+
'/screenshots/s3-0412.png',
16+
'/screenshots/s4-0412.png',
17+
'/screenshots/s5-0412.png',
18+
'/screenshots/s6-0412.png',
19+
'/screenshots/s7-0412.png',
20+
'/screenshots/s8-0412.png',
21+
'/screenshots/s9-0412.png',
22+
'/screenshots/s10-0412.png',
23+
];
24+
</script>
25+
26+
<template>
27+
<div class="carousel-section">
28+
<Swiper
29+
:modules="modules"
30+
:slides-per-view="1"
31+
:space-between="16"
32+
:loop="true"
33+
:autoplay="{ delay: 4000, disableOnInteraction: false, pauseOnMouseEnter: true }"
34+
:pagination="{ clickable: true }"
35+
:navigation="{ nextEl: '.carousel-next', prevEl: '.carousel-prev' }"
36+
class="screenshots-swiper"
37+
>
38+
<SwiperSlide v-for="(src, idx) in screenshots" :key="idx">
39+
<div class="slide-wrapper">
40+
<img :src="src" :alt="`AQBot Screenshot ${idx + 1}`" loading="lazy" />
41+
</div>
42+
</SwiperSlide>
43+
</Swiper>
44+
<button class="carousel-nav carousel-prev" aria-label="Previous slide">
45+
<ChevronLeft :size="24" />
46+
</button>
47+
<button class="carousel-nav carousel-next" aria-label="Next slide">
48+
<ChevronRight :size="24" />
49+
</button>
50+
</div>
51+
</template>
52+
53+
<style scoped>
54+
.carousel-section {
55+
position: relative;
56+
max-width: 960px;
57+
margin: 40px auto 24px;
58+
padding: 0 24px;
59+
overflow: hidden;
60+
}
61+
62+
@media (min-width: 640px) {
63+
.carousel-section {
64+
padding: 0 48px;
65+
}
66+
}
67+
68+
@media (min-width: 960px) {
69+
.carousel-section {
70+
padding: 0 64px;
71+
}
72+
}
73+
74+
.screenshots-swiper {
75+
border-radius: 12px;
76+
overflow: hidden;
77+
padding-bottom: 40px;
78+
}
79+
80+
.slide-wrapper {
81+
border-radius: 12px;
82+
overflow: hidden;
83+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
84+
background: var(--vp-c-bg-soft);
85+
transition: transform 0.3s ease, box-shadow 0.3s ease;
86+
}
87+
88+
.slide-wrapper:hover {
89+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
90+
}
91+
92+
.dark .slide-wrapper {
93+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
94+
}
95+
.dark .slide-wrapper:hover {
96+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
97+
}
98+
99+
.slide-wrapper img {
100+
width: 100%;
101+
height: auto;
102+
display: block;
103+
}
104+
105+
/* Navigation arrows */
106+
.carousel-nav {
107+
position: absolute;
108+
top: calc(50% - 20px);
109+
transform: translateY(-50%);
110+
z-index: 10;
111+
width: 44px;
112+
height: 44px;
113+
border-radius: 50%;
114+
border: 1px solid var(--vp-c-divider);
115+
background: var(--vp-c-bg);
116+
color: var(--vp-c-text-2);
117+
display: flex;
118+
align-items: center;
119+
justify-content: center;
120+
cursor: pointer;
121+
transition: all 0.2s ease;
122+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
123+
}
124+
125+
.carousel-nav:hover {
126+
border-color: var(--vp-c-brand-1);
127+
color: var(--vp-c-brand-1);
128+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
129+
}
130+
131+
.dark .carousel-nav {
132+
background: var(--vp-c-bg-soft);
133+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
134+
}
135+
136+
.carousel-prev {
137+
left: 4px;
138+
}
139+
140+
.carousel-next {
141+
right: 4px;
142+
}
143+
144+
@media (min-width: 640px) {
145+
.carousel-prev { left: 16px; }
146+
.carousel-next { right: 16px; }
147+
}
148+
149+
@media (min-width: 960px) {
150+
.carousel-prev { left: 28px; }
151+
.carousel-next { right: 28px; }
152+
}
153+
154+
@media (max-width: 640px) {
155+
.carousel-nav {
156+
display: none;
157+
}
158+
}
159+
160+
/* Swiper pagination bullets */
161+
:deep(.swiper-pagination-bullet) {
162+
background: var(--vp-c-text-3);
163+
opacity: 0.4;
164+
width: 8px;
165+
height: 8px;
166+
transition: all 0.25s ease;
167+
}
168+
169+
:deep(.swiper-pagination-bullet-active) {
170+
background: var(--vp-c-brand-1);
171+
opacity: 1;
172+
width: 24px;
173+
border-radius: 4px;
174+
}
175+
</style>

0 commit comments

Comments
 (0)