259 lines
11 KiB
Vue
259 lines
11 KiB
Vue
<template>
|
||
<section id="services" class="page-section block animate-slide-down">
|
||
<div class="py-16 text-center">
|
||
<h2 class="font-serif text-5xl italic text-white mb-6">共创数字未来</h2>
|
||
<p class="text-art-muted text-lg max-w-2xl mx-auto">用代码构建骨架,用设计触动灵魂。</p>
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-32 px-4" id="services-grid">
|
||
<div class="tilt-card group h-96">
|
||
<div class="tilt-inner flex flex-col justify-between">
|
||
<div class="w-16 h-16 rounded-2xl bg-blue-500/10 flex items-center justify-center text-blue-400 mb-6 border border-blue-500/20 group-hover:scale-110 transition-transform duration-500">
|
||
<i data-lucide="layers" class="w-8 h-8"></i>
|
||
</div>
|
||
<div>
|
||
<h3 class="text-2xl font-bold text-white mb-4">前端架构设计</h3>
|
||
<p class="text-art-muted text-sm leading-relaxed">为复杂的大型应用提供可扩展的架构方案。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="tilt-card group h-96">
|
||
<div class="tilt-inner flex flex-col justify-between border-art-accent/20 bg-white/5">
|
||
<div class="w-16 h-16 rounded-2xl bg-art-accent/10 flex items-center justify-center text-art-accent mb-6 border border-art-accent/20 group-hover:scale-110 transition-transform duration-500 shadow-[0_0_30px_-10px_rgba(212,179,131,0.3)]">
|
||
<i data-lucide="wand-2" class="w-8 h-8"></i>
|
||
</div>
|
||
<div>
|
||
<h3 class="text-2xl font-bold text-white mb-4">创意交互开发</h3>
|
||
<p class="text-art-muted text-sm leading-relaxed">利用 WebGL (Three.js) 和 GSAP 打造令人过目难忘的着陆页。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="tilt-card group h-96">
|
||
<div class="tilt-inner flex flex-col justify-between">
|
||
<div class="w-16 h-16 rounded-2xl bg-purple-500/10 flex items-center justify-center text-purple-400 mb-6 border border-purple-500/20 group-hover:scale-110 transition-transform duration-500">
|
||
<i data-lucide="smartphone" class="w-8 h-8"></i>
|
||
</div>
|
||
<div>
|
||
<h3 class="text-2xl font-bold text-white mb-4">跨平台应用</h3>
|
||
<p class="text-art-muted text-sm leading-relaxed">使用 UniApp 或 React Native 开发高质量的移动端应用。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Process Accordion -->
|
||
<div class="mb-32 px-4">
|
||
<h3 class="font-serif text-3xl text-white text-center mb-12 italic">创作流程</h3>
|
||
<div class="process-accordion">
|
||
<div class="process-step">
|
||
<img src="https://images.unsplash.com/photo-1531403009284-440f080d1e12?q=80&w=2670&auto=format&fit=crop" class="step-bg" alt="Discovery">
|
||
<div class="step-content">
|
||
<div class="step-number">01</div>
|
||
<h4 class="text-xl font-bold text-white mb-2">灵感 & 探索</h4>
|
||
<p class="step-desc">深入理解业务需求,进行竞品分析,寻找视觉灵感,确定设计方向。这是地基。</p>
|
||
</div>
|
||
</div>
|
||
<div class="process-step">
|
||
<img src="https://images.unsplash.com/photo-1581291518633-83b4ebd1d83e?q=80&w=2670&auto=format&fit=crop" class="step-bg" alt="Design">
|
||
<div class="step-content">
|
||
<div class="step-number">02</div>
|
||
<h4 class="text-xl font-bold text-white mb-2">架构 & 设计</h4>
|
||
<p class="step-desc">设计高保真原型,规划技术架构,确定数据流向。将抽象的想法具象化。</p>
|
||
</div>
|
||
</div>
|
||
<div class="process-step">
|
||
<img src="https://images.unsplash.com/photo-1555099962-4199c345e5dd?q=80&w=2670&auto=format&fit=crop" class="step-bg" alt="Code">
|
||
<div class="step-content">
|
||
<div class="step-number">03</div>
|
||
<h4 class="text-xl font-bold text-white mb-2">编码 & 雕琢</h4>
|
||
<p class="step-desc">编写干净、可维护的代码。添加微交互和动画,让页面"活"起来。</p>
|
||
</div>
|
||
</div>
|
||
<div class="process-step">
|
||
<img src="https://images.unsplash.com/photo-1460925895917-afdab827c52f?q=80&w=2426&auto=format&fit=crop" class="step-bg" alt="Launch">
|
||
<div class="step-content">
|
||
<div class="step-number">04</div>
|
||
<h4 class="text-xl font-bold text-white mb-2">测试 & 交付</h4>
|
||
<p class="step-desc">多设备测试,性能优化,SEO 配置。确保最终交付物完美无瑕。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Danmaku & Partners -->
|
||
<div class="py-20 mb-32 overflow-hidden relative">
|
||
<div class="absolute left-0 top-0 w-20 h-full bg-gradient-to-r from-art-bg to-transparent z-10"></div>
|
||
<div class="absolute right-0 top-0 w-20 h-full bg-gradient-to-l from-art-bg to-transparent z-10"></div>
|
||
<h3 class="text-3xl font-serif text-white text-center mb-12 italic">客户原声</h3>
|
||
<div v-if="loading" class="flex justify-center items-center h-64">
|
||
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-art-accent"></div>
|
||
</div>
|
||
<div v-else-if="error" class="text-center text-red-500 py-20">
|
||
<p class="mb-4">{{ error }}</p>
|
||
<button @click="fetchServicesData" class="px-4 py-2 bg-art-accent text-white rounded hover:bg-opacity-80 transition-colors">
|
||
重试
|
||
</button>
|
||
</div>
|
||
<div v-else class="flex flex-col gap-6" id="danmaku-container">
|
||
<!-- Danmaku items will be rendered here -->
|
||
<div class="danmaku-row animate-marquee hover:[animation-play-state:paused]">
|
||
<div v-for="(testimonial, index) in testimonials" :key="testimonial.id" class="danmaku-item">
|
||
<div class="w-6 h-6 rounded-full bg-blue-500/20"></div>
|
||
<span>"{{ testimonial.content }}"</span>
|
||
</div>
|
||
<!-- 复制一份数据以实现无缝滚动 -->
|
||
<div v-for="(testimonial, index) in testimonials" :key="testimonial.id + '-copy'" class="danmaku-item">
|
||
<div class="w-6 h-6 rounded-full bg-blue-500/20"></div>
|
||
<span>"{{ testimonial.content }}"</span>
|
||
</div>
|
||
</div>
|
||
<div class="danmaku-row animate-marquee-reverse hover:[animation-play-state:paused]">
|
||
<div v-for="(testimonial, index) in testimonials" :key="testimonial.id" class="danmaku-item">
|
||
<div class="w-6 h-6 rounded-full bg-purple-500/20"></div>
|
||
<span>"{{ testimonial.content }}"</span>
|
||
</div>
|
||
<!-- 复制一份数据以实现无缝滚动 -->
|
||
<div v-for="(testimonial, index) in testimonials" :key="testimonial.id + '-copy'" class="danmaku-item">
|
||
<div class="w-6 h-6 rounded-full bg-purple-500/20"></div>
|
||
<span>"{{ testimonial.content }}"</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- CTA -->
|
||
<div class="max-w-4xl mx-auto text-center px-6 mb-20">
|
||
<div class="p-12 rounded-3xl bg-gradient-to-b from-white/10 to-transparent border border-white/10 relative overflow-hidden">
|
||
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-64 h-64 bg-art-accent/20 blur-[100px] -z-10"></div>
|
||
<h2 class="text-4xl md:text-5xl font-serif text-white mb-6">准备好开始了吗?</h2>
|
||
<p class="text-art-muted mb-8 max-w-xl mx-auto">无论是一个疯狂的想法,还是一个具体的业务需求,我都乐意倾听。</p>
|
||
<button @click="openInquiry" class="inline-flex items-center gap-2 px-8 py-4 bg-white text-black rounded-full font-bold hover:scale-105 transition-transform"><span>发起合作咨询</span><i data-lucide="arrow-right" class="w-5 h-5"></i></button>
|
||
</div>
|
||
</div>
|
||
<!-- Partners -->
|
||
<div class="max-w-4xl mx-auto px-6 mb-32 border-t border-white/5 pt-12">
|
||
<p class="text-center text-xs font-mono text-art-muted tracking-widest uppercase mb-8">Trusted By</p>
|
||
<div v-if="loading" class="flex justify-center items-center h-64">
|
||
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-art-accent"></div>
|
||
</div>
|
||
<div v-else-if="error" class="text-center text-red-500 py-20">
|
||
<p class="mb-4">{{ error }}</p>
|
||
<button @click="fetchServicesData" class="px-4 py-2 bg-art-accent text-white rounded hover:bg-opacity-80 transition-colors">
|
||
重试
|
||
</button>
|
||
</div>
|
||
<div v-else class="flex flex-wrap justify-center gap-8 md:gap-16 opacity-50">
|
||
<span
|
||
v-for="partner in partners"
|
||
:key="partner.id"
|
||
class="partner-logo text-xl font-bold text-white cursor-pointer"
|
||
:class="getPartnerFontClass(partner.name)"
|
||
>
|
||
{{ partner.name }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, onBeforeMount, onUpdated, nextTick } from 'vue'
|
||
import { useScrollAnimation } from '../composables/useScrollAnimation'
|
||
import {
|
||
fetchTestimonials,
|
||
fetchPartners,
|
||
type Testimonial,
|
||
type Partner
|
||
} from '../services/api'
|
||
|
||
const testimonials = ref<Testimonial[]>([])
|
||
const partners = ref<Partner[]>([])
|
||
const loading = ref(false)
|
||
const error = ref('')
|
||
|
||
const refreshIcons = () => {
|
||
if ((window as any).lucide) {
|
||
(window as any).lucide.createIcons()
|
||
}
|
||
}
|
||
|
||
// 获取服务数据
|
||
const fetchServicesData = async () => {
|
||
loading.value = true
|
||
error.value = ''
|
||
try {
|
||
const [testimonialsData, partnersData] = await Promise.all([
|
||
fetchTestimonials(),
|
||
fetchPartners()
|
||
])
|
||
testimonials.value = testimonialsData
|
||
partners.value = partnersData
|
||
} catch (err) {
|
||
console.error('Error fetching services data:', err)
|
||
error.value = '获取服务数据失败,请稍后重试'
|
||
} finally {
|
||
loading.value = false
|
||
nextTick(refreshIcons)
|
||
}
|
||
}
|
||
|
||
// 获取合作伙伴字体类
|
||
const getPartnerFontClass = (name: string): string => {
|
||
if (name === 'VOGUE' || name === 'Monocle') {
|
||
return 'font-serif italic'
|
||
} else if (name === 'WIRED') {
|
||
return 'font-mono'
|
||
} else if (name === 'stripe') {
|
||
return 'font-sans'
|
||
}
|
||
return 'font-bold'
|
||
}
|
||
|
||
const openInquiry = () => {
|
||
// 使用全局函数打开咨询模态框
|
||
if ((window as any).openInquiry) {
|
||
(window as any).openInquiry()
|
||
}
|
||
}
|
||
|
||
onBeforeMount(() => {
|
||
fetchServicesData()
|
||
})
|
||
|
||
const { initObserver } = useScrollAnimation()
|
||
|
||
onMounted(() => {
|
||
// 启用滚动动画
|
||
initObserver()
|
||
|
||
// 初始化Lucide图标
|
||
refreshIcons()
|
||
|
||
// 添加鼠标倾斜效果
|
||
const tiltCards = document.querySelectorAll('.tilt-card')
|
||
tiltCards.forEach(card => {
|
||
card.addEventListener('mousemove', (e: any) => {
|
||
const cardRect = card.getBoundingClientRect()
|
||
const x = e.clientX - cardRect.left
|
||
const y = e.clientY - cardRect.top
|
||
|
||
const xPercent = (x / cardRect.width) * 100
|
||
const yPercent = (y / cardRect.height) * 100
|
||
|
||
;(card as HTMLElement).style.setProperty('--mouse-x', `${xPercent}%`)
|
||
;(card as HTMLElement).style.setProperty('--mouse-y', `${yPercent}%`)
|
||
|
||
// 3D Rotation
|
||
const rotateX = (y / cardRect.height - 0.5) * 20
|
||
const rotateY = (x / cardRect.width - 0.5) * -20
|
||
;(card as HTMLElement).style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
|
||
})
|
||
|
||
card.addEventListener('mouseleave', () => {
|
||
;(card as HTMLElement).style.transform = 'perspective(1000px) rotateX(0) rotateY(0)'
|
||
})
|
||
})
|
||
})
|
||
|
||
onUpdated(() => {
|
||
refreshIcons()
|
||
})
|
||
</script> |