Skip to content

Commit 0910480

Browse files
Ncerzzkclaude
andcommitted
Add GitHub Pages deployment and product carousel
- Add GitHub Actions workflow for automatic deployment - Create Vue product carousel component with auto-play - Update homepage with product showcase - Add product introduction page with images from submodules - Update site branding to 座头鲸工作室 (HumpbackLab) - Add deployment documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7c69d92 commit 0910480

8 files changed

Lines changed: 573 additions & 15 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: false
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
with:
24+
fetch-depth: 0
25+
submodules: recursive
26+
27+
- name: Setup Node
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: 20
31+
cache: npm
32+
33+
- name: Setup Typst
34+
run: |
35+
curl -fsSL https://github.com/typst/typst/releases/download/v0.12.0/typst-x86_64-unknown-linux-musl.tar.xz -o typst.tar.xz
36+
tar -xf typst.tar.xz
37+
sudo mv typst-x86_64-unknown-linux-musl/typst /usr/local/bin/
38+
typst --version
39+
40+
- name: Install fonts
41+
run: |
42+
sudo apt-get update
43+
sudo apt-get install -y fonts-noto-core fonts-noto-cjk
44+
45+
- name: Install dependencies
46+
run: npm ci
47+
48+
- name: Build manuals
49+
run: npm run build:manuals
50+
51+
- name: Build VitePress site
52+
run: npm run build
53+
54+
- name: Setup Pages
55+
uses: actions/configure-pages@v4
56+
57+
- name: Upload artifact
58+
uses: actions/upload-pages-artifact@v3
59+
with:
60+
path: .vitepress/dist
61+
62+
deploy:
63+
environment:
64+
name: github-pages
65+
url: ${{ steps.deployment.outputs.page_url }}
66+
needs: build
67+
runs-on: ubuntu-latest
68+
name: Deploy
69+
steps:
70+
- name: Deploy to GitHub Pages
71+
id: deployment
72+
uses: actions/deploy-pages@v4

.vitepress/config.mts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { defineConfig } from 'vitepress'
22

33
export default defineConfig({
4-
title: "Website",
5-
description: "A VitePress Site",
4+
title: "座头鲸工作室",
5+
description: "HumpbackLab",
66
themeConfig: {
77
nav: [
88
{ text: 'Home', link: '/' },
9+
{ text: '产品介绍', link: '/products' },
910
{ text: '用户手册', link: '/manuals' }
1011
],
1112
sidebar: [],
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
<script setup lang="ts">
2+
import { ref, onMounted, onUnmounted } from 'vue'
3+
4+
const products = [
5+
{
6+
name: 'Android-ELRS 转换器',
7+
description: '将安卓手机变成专业遥控器,支持虚拟摇杆和体感控制,即插即用,内置电池充电功能',
8+
image: '/manuals/Android-ELRS-manual/assets/1.png',
9+
link: '/products#android-elrs-转换器',
10+
manual: '/manuals/android-elrs-manual.pdf'
11+
},
12+
{
13+
name: 'AT32F435mini 飞控',
14+
description: '1S 轻量级一体式飞控,集成 ELRS 接收机,适合差速固定翼和纸飞机改装',
15+
image: '/manuals/flight-controller-manual/assets/product-overview.png',
16+
link: '/products#at32f435mini-inav-飞控',
17+
manual: '/manuals/flight-controller-manual.pdf'
18+
}
19+
]
20+
21+
const currentIndex = ref(0)
22+
let intervalId: number | null = null
23+
24+
const nextSlide = () => {
25+
currentIndex.value = (currentIndex.value + 1) % products.length
26+
}
27+
28+
const prevSlide = () => {
29+
currentIndex.value = (currentIndex.value - 1 + products.length) % products.length
30+
}
31+
32+
const goToSlide = (index: number) => {
33+
currentIndex.value = index
34+
}
35+
36+
onMounted(() => {
37+
intervalId = setInterval(nextSlide, 5000)
38+
})
39+
40+
onUnmounted(() => {
41+
if (intervalId) clearInterval(intervalId)
42+
})
43+
</script>
44+
45+
<template>
46+
<div class="product-carousel">
47+
<h2>我们的产品</h2>
48+
49+
<div class="carousel-container">
50+
<button class="carousel-btn prev" @click="prevSlide" aria-label="上一个">‹</button>
51+
52+
<div class="carousel-content">
53+
<transition name="slide" mode="out-in">
54+
<div :key="currentIndex" class="product-slide">
55+
<div class="product-grid">
56+
<div class="product-image">
57+
<img :src="products[currentIndex].image" :alt="products[currentIndex].name">
58+
</div>
59+
<div class="product-info">
60+
<h3>{{ products[currentIndex].name }}</h3>
61+
<p>{{ products[currentIndex].description }}</p>
62+
<div class="product-actions">
63+
<a :href="products[currentIndex].link" class="btn-primary">了解详情</a>
64+
<a :href="products[currentIndex].manual" class="btn-secondary">下载手册</a>
65+
</div>
66+
</div>
67+
</div>
68+
</div>
69+
</transition>
70+
</div>
71+
72+
<button class="carousel-btn next" @click="nextSlide" aria-label="下一个">›</button>
73+
</div>
74+
75+
<div class="carousel-dots">
76+
<button
77+
v-for="(product, index) in products"
78+
:key="index"
79+
:class="['dot', { active: currentIndex === index }]"
80+
@click="goToSlide(index)"
81+
:aria-label="`第 ${index + 1} 个产品`"
82+
/>
83+
</div>
84+
</div>
85+
</template>
86+
87+
<style scoped>
88+
.product-carousel {
89+
max-width: 1200px;
90+
margin: 4rem auto;
91+
padding: 0 24px;
92+
}
93+
94+
.product-carousel h2 {
95+
text-align: center;
96+
font-size: 2rem;
97+
margin-bottom: 3rem;
98+
color: var(--vp-c-text-1);
99+
}
100+
101+
.carousel-container {
102+
position: relative;
103+
display: flex;
104+
align-items: center;
105+
gap: 1rem;
106+
}
107+
108+
.carousel-content {
109+
flex: 1;
110+
overflow: hidden;
111+
border-radius: 12px;
112+
border: 1px solid var(--vp-c-divider);
113+
background: var(--vp-c-bg-soft);
114+
}
115+
116+
.product-slide {
117+
padding: 2rem;
118+
}
119+
120+
.product-grid {
121+
display: grid;
122+
grid-template-columns: 1fr 1fr;
123+
gap: 2rem;
124+
align-items: center;
125+
}
126+
127+
.product-image img {
128+
width: 100%;
129+
height: auto;
130+
border-radius: 8px;
131+
object-fit: cover;
132+
}
133+
134+
.product-info h3 {
135+
font-size: 1.75rem;
136+
margin: 0 0 1rem 0;
137+
color: var(--vp-c-text-1);
138+
}
139+
140+
.product-info p {
141+
color: var(--vp-c-text-2);
142+
line-height: 1.6;
143+
margin-bottom: 1.5rem;
144+
}
145+
146+
.product-actions {
147+
display: flex;
148+
gap: 1rem;
149+
}
150+
151+
.product-actions a {
152+
padding: 0.6rem 1.5rem;
153+
border-radius: 8px;
154+
text-decoration: none;
155+
font-size: 0.95rem;
156+
transition: all 0.3s;
157+
display: inline-block;
158+
}
159+
160+
.btn-primary {
161+
background: var(--vp-c-brand);
162+
color: white;
163+
}
164+
165+
.btn-primary:hover {
166+
background: var(--vp-c-brand-dark);
167+
transform: translateY(-2px);
168+
}
169+
170+
.btn-secondary {
171+
border: 1px solid var(--vp-c-divider);
172+
color: var(--vp-c-text-1);
173+
}
174+
175+
.btn-secondary:hover {
176+
border-color: var(--vp-c-brand);
177+
color: var(--vp-c-brand);
178+
}
179+
180+
.carousel-btn {
181+
background: var(--vp-c-bg);
182+
border: 1px solid var(--vp-c-divider);
183+
width: 48px;
184+
height: 48px;
185+
border-radius: 50%;
186+
font-size: 2rem;
187+
cursor: pointer;
188+
transition: all 0.3s;
189+
color: var(--vp-c-text-1);
190+
display: flex;
191+
align-items: center;
192+
justify-content: center;
193+
}
194+
195+
.carousel-btn:hover {
196+
background: var(--vp-c-brand);
197+
color: white;
198+
border-color: var(--vp-c-brand);
199+
}
200+
201+
.carousel-dots {
202+
display: flex;
203+
justify-content: center;
204+
gap: 0.5rem;
205+
margin-top: 2rem;
206+
}
207+
208+
.dot {
209+
width: 12px;
210+
height: 12px;
211+
border-radius: 50%;
212+
border: 2px solid var(--vp-c-divider);
213+
background: transparent;
214+
cursor: pointer;
215+
transition: all 0.3s;
216+
}
217+
218+
.dot.active {
219+
background: var(--vp-c-brand);
220+
border-color: var(--vp-c-brand);
221+
}
222+
223+
.dot:hover {
224+
border-color: var(--vp-c-brand);
225+
}
226+
227+
.slide-enter-active,
228+
.slide-leave-active {
229+
transition: all 0.5s ease;
230+
}
231+
232+
.slide-enter-from {
233+
opacity: 0;
234+
transform: translateX(30px);
235+
}
236+
237+
.slide-leave-to {
238+
opacity: 0;
239+
transform: translateX(-30px);
240+
}
241+
242+
@media (max-width: 768px) {
243+
.product-grid {
244+
grid-template-columns: 1fr;
245+
}
246+
247+
.carousel-btn {
248+
display: none;
249+
}
250+
}
251+
</style>

.vitepress/theme/custom.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/* 全局自定义样式 */

.vitepress/theme/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import DefaultTheme from 'vitepress/theme'
2+
import ProductCarousel from './components/ProductCarousel.vue'
3+
import './custom.css'
4+
5+
export default {
6+
extends: DefaultTheme,
7+
enhanceApp({ app }) {
8+
app.component('ProductCarousel', ProductCarousel)
9+
}
10+
}

0 commit comments

Comments
 (0)