init
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
<script setup>
|
||||
import { advantages as defaultAdvantages } from "~/data/advantages";
|
||||
|
||||
defineProps({
|
||||
advantages: {
|
||||
type: Array,
|
||||
default: () => defaultAdvantages,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "四大核心优势",
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "从真人内容生产、媒体矩阵到增长闭环与行业定制能力,帮助用户快速理解大马棒的差异化竞争力。",
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
preview: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "advantages",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-white py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">Advantages</p>
|
||||
<h2 class="section-title mt-4">{{ title }}</h2>
|
||||
<p class="mt-4 text-base leading-8 text-[#666]">{{ description }}</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-3 md:items-end">
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
<NuxtLink v-if="preview" to="/advantages" class="text-sm font-medium text-gm-green">
|
||||
查看核心优势 →
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-5 md:grid-cols-2 xl:grid-cols-4" data-stagger>
|
||||
<article
|
||||
v-for="item in advantages"
|
||||
:key="item.slug"
|
||||
class="rounded-lg border border-gm-border bg-white p-6 shadow-sm"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<p class="text-[28px] font-semibold leading-none text-gm-green">{{ item.metric }}</p>
|
||||
<h3 class="mt-5 text-[20px] font-semibold text-[#262626]">{{ item.title }}</h3>
|
||||
<p class="mt-3 text-sm font-medium leading-7 text-[#333]">{{ item.point }}</p>
|
||||
<p class="mt-3 text-sm leading-7 text-[#666]">{{ item.value }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,68 @@
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { insights, insightCategories } from "~/data/insights";
|
||||
|
||||
const categories = insightCategories.filter((category) => category !== "全部");
|
||||
const activeCategory = ref(categories[0]);
|
||||
|
||||
const filteredArticles = computed(() =>
|
||||
insights.filter((article) => article.category === activeCategory.value),
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-[#fafafa] py-20" id="insights">
|
||||
<div class="container-gm">
|
||||
<div class="text-center" data-reveal>
|
||||
<p class="text-sm text-[#a1a1a1]">聚焦于</p>
|
||||
<h2 class="section-title mt-4">原创观点</h2>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex flex-wrap justify-center gap-3" data-reveal>
|
||||
<button
|
||||
v-for="category in categories"
|
||||
:key="category"
|
||||
type="button"
|
||||
class="rounded-full border px-6 py-2 text-sm transition-all duration-300"
|
||||
:class="
|
||||
activeCategory === category
|
||||
? 'border-gm-green bg-gm-green text-white'
|
||||
: 'border-gm-border bg-white text-[#666] hover:border-[#d5d5d5]'
|
||||
"
|
||||
@click="activeCategory = category"
|
||||
>
|
||||
{{ category }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-6 md:grid-cols-2 xl:grid-cols-3" data-stagger>
|
||||
<article
|
||||
v-for="article in filteredArticles"
|
||||
:key="article.title"
|
||||
class="overflow-hidden rounded-[28px] border border-gm-border bg-white shadow-sm transition hover:-translate-y-1 hover:shadow-lg"
|
||||
data-stagger-item
|
||||
>
|
||||
<div class="h-56 overflow-hidden bg-gm-light">
|
||||
<img
|
||||
:src="article.image"
|
||||
:alt="article.title"
|
||||
class="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<p class="text-xs font-medium text-gm-green">{{ article.category }}</p>
|
||||
<h3 class="mt-3 text-lg font-semibold leading-7 text-[#262626]">
|
||||
{{ article.title }}
|
||||
</h3>
|
||||
<p class="mt-3 text-xs text-[#9b9b9b]">{{ article.date }}</p>
|
||||
<NuxtLink to="/insights" class="mt-4 inline-flex items-center gap-2 text-sm font-medium text-gm-green">
|
||||
查看详情
|
||||
<span aria-hidden="true">→</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
capabilities: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-[#f7f8f7] py-20">
|
||||
<div class="container-gm">
|
||||
<div class="text-center" data-reveal>
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#a1a1a1]">Capability</p>
|
||||
<h2 class="section-title mt-4">从项目交付到增长资产</h2>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 overflow-x-auto rounded-[28px] border border-gm-border bg-white" data-reveal>
|
||||
<div class="min-w-[720px]">
|
||||
<div class="grid grid-cols-[0.8fr_1fr_1fr] border-b border-gm-border bg-gm-dark text-sm font-medium text-white">
|
||||
<div class="p-5">能力维度</div>
|
||||
<div class="p-5">基础交付</div>
|
||||
<div class="p-5">增长超人升级</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in capabilities"
|
||||
:key="item.label"
|
||||
class="grid grid-cols-[0.8fr_1fr_1fr] border-b border-gm-border last:border-b-0"
|
||||
>
|
||||
<div class="p-5 font-medium text-[#262626]">{{ item.label }}</div>
|
||||
<div class="p-5 text-sm leading-7 text-[#777]">{{ item.basic }}</div>
|
||||
<div class="p-5 text-sm font-medium leading-7 text-gm-green">{{ item.advanced }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,150 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { caseCategories, cases as defaultCases } from "~/data/cases";
|
||||
|
||||
const props = defineProps({
|
||||
cases: {
|
||||
type: Array,
|
||||
default: () => defaultCases,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
previewLimit: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
preview: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "cases",
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const activeCategory = ref("全部");
|
||||
|
||||
const visibleCategories = computed(() =>
|
||||
props.preview ? ["全部"] : caseCategories,
|
||||
);
|
||||
|
||||
const filteredCases = computed(() => {
|
||||
const items =
|
||||
activeCategory.value === "全部"
|
||||
? props.cases
|
||||
: props.cases.filter((item) => item.category === activeCategory.value);
|
||||
|
||||
return props.preview
|
||||
? items.filter((item) => item.featured).slice(0, props.previewLimit)
|
||||
: items;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.query.category,
|
||||
(category) => {
|
||||
activeCategory.value =
|
||||
typeof category === "string" && caseCategories.includes(category)
|
||||
? category
|
||||
: "全部";
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-[#f7f8f7] py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-6 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#9a9a9a]">Cases</p>
|
||||
<h2 class="section-title mt-4">
|
||||
{{ title || (preview ? "真实落地成果,见证增长实效" : "案例库") }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-3 md:items-end">
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
<NuxtLink
|
||||
v-if="preview"
|
||||
to="/cases"
|
||||
class="inline-flex items-center gap-2 text-sm font-medium text-gm-green"
|
||||
>
|
||||
查看全部案例 <span aria-hidden="true">→</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!preview" class="mt-8 flex flex-wrap gap-3" data-reveal>
|
||||
<button
|
||||
v-for="category in visibleCategories"
|
||||
:key="category"
|
||||
type="button"
|
||||
class="rounded-full border px-6 py-2 text-sm transition-all duration-300"
|
||||
:class="
|
||||
activeCategory === category
|
||||
? 'border-gm-green bg-gm-green text-white'
|
||||
: 'border-gm-border bg-white text-[#666] hover:border-[#d5d5d5]'
|
||||
"
|
||||
@click="activeCategory = category"
|
||||
>
|
||||
{{ category }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-6 md:grid-cols-2 xl:grid-cols-3" data-stagger>
|
||||
<NuxtLink
|
||||
v-for="item in filteredCases"
|
||||
:key="item.title"
|
||||
:to="`/cases/${item.slug}`"
|
||||
class="overflow-hidden rounded-lg border border-gm-border bg-white shadow-sm transition hover:shadow-hover"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<div class="h-56 overflow-hidden bg-gm-light">
|
||||
<img
|
||||
:src="item.image"
|
||||
:alt="item.title"
|
||||
class="h-full w-full object-cover transition duration-500 hover:scale-105"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<p class="text-xs font-medium text-gm-green">{{ item.category }}</p>
|
||||
<h3 class="mt-3 text-xl font-semibold leading-7 text-[#262626]">
|
||||
{{ item.title }}
|
||||
</h3>
|
||||
<p class="mt-3 text-sm leading-7 text-[#777]">{{ item.summary }}</p>
|
||||
<div class="mt-5 flex flex-wrap gap-2">
|
||||
<span
|
||||
v-for="tag in item.tags"
|
||||
:key="tag"
|
||||
class="rounded-full bg-gm-light px-3 py-1 text-xs text-[#666]"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-6 grid gap-2">
|
||||
<p
|
||||
v-for="metric in item.metrics"
|
||||
:key="metric"
|
||||
class="text-sm font-medium text-[#262626]"
|
||||
>
|
||||
{{ metric }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-5 font-mono text-[11px] text-[#999]">
|
||||
案例路径 {{ item.path }}
|
||||
</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { clientLogos as defaultLogos } from "~/data/home";
|
||||
|
||||
const props = defineProps({
|
||||
logos: {
|
||||
type: Array,
|
||||
default: () => defaultLogos,
|
||||
},
|
||||
});
|
||||
|
||||
const marqueeTrackA = computed(() => [...props.logos, ...props.logos]);
|
||||
const marqueeTrackB = computed(() => [...props.logos, ...props.logos]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="overflow-hidden bg-[#fafafa] py-16" id="cases">
|
||||
<div class="container-gm" data-reveal>
|
||||
<p class="text-center text-sm uppercase tracking-[0.3em] text-[#8a8a8a]">
|
||||
我们的客户
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="relative mt-8 overflow-hidden">
|
||||
<div class="flex w-max animate-marquee hover:[animation-play-state:paused]">
|
||||
<div
|
||||
v-for="(name, index) in marqueeTrackA"
|
||||
:key="`a-${name}-${index}`"
|
||||
class="mx-4 flex h-14 w-32 shrink-0 items-center justify-center rounded-2xl border border-gm-border bg-white text-sm font-medium text-[#6b6b6b] shadow-sm"
|
||||
>
|
||||
{{ name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative mt-6 overflow-hidden">
|
||||
<div class="flex w-max animate-marquee-reverse hover:[animation-play-state:paused]">
|
||||
<div
|
||||
v-for="(name, index) in marqueeTrackB"
|
||||
:key="`b-${name}-${index}`"
|
||||
class="mx-4 flex h-14 w-32 shrink-0 items-center justify-center rounded-2xl border border-gm-border bg-white text-sm font-medium text-[#6b6b6b] shadow-sm"
|
||||
>
|
||||
{{ name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup>
|
||||
import { homeHero as defaultHero } from "~/data/home";
|
||||
|
||||
defineProps({
|
||||
hero: {
|
||||
type: Object,
|
||||
default: () => defaultHero,
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "hero",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
:id="sectionId"
|
||||
class="relative flex min-h-[calc(100vh-var(--page-offset,134px)-40px)] items-center overflow-hidden bg-black bg-cover bg-center pb-10 text-white"
|
||||
:style="{ backgroundImage: `url(${hero.backgroundImage})` }"
|
||||
>
|
||||
<div class="absolute inset-0 bg-black/40" />
|
||||
<div class="absolute inset-0 bg-[radial-gradient(circle_at_80%_20%,rgba(27,214,41,0.18),transparent_32%),radial-gradient(circle_at_82%_78%,rgba(0,92,230,0.18),transparent_26%)]" />
|
||||
<div class="absolute inset-x-0 bottom-0 h-24 bg-gradient-to-t from-white to-transparent" />
|
||||
|
||||
<div class="container-gm relative z-10 grid gap-12 py-16 md:py-20 lg:grid-cols-[1.05fr_0.95fr] lg:items-center">
|
||||
<div>
|
||||
<p class="text-sm uppercase tracking-[0.34em] text-white/70" data-hero-reveal>
|
||||
{{ hero.eyebrow }}
|
||||
</p>
|
||||
<h1 class="mt-5 max-w-4xl text-balance text-[36px] font-semibold leading-tight md:text-[42px]" data-hero-reveal>
|
||||
{{ hero.title }}
|
||||
</h1>
|
||||
<p class="mt-5 max-w-2xl text-base leading-8 text-white/82 md:text-lg" data-hero-reveal>
|
||||
{{ hero.description }}
|
||||
</p>
|
||||
|
||||
<div class="mt-8 flex flex-wrap gap-3" data-hero-reveal>
|
||||
<span
|
||||
v-for="tag in hero.tags"
|
||||
:key="tag"
|
||||
class="rounded border border-white/20 bg-white/10 px-4 py-2 text-xs text-white/82 backdrop-blur"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 flex flex-wrap gap-4" data-hero-reveal>
|
||||
<NuxtLink :to="hero.primaryCta.href" class="gm-btn gm-btn-primary px-8">
|
||||
{{ hero.primaryCta.label }}
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
:to="hero.secondaryCta.href"
|
||||
class="gm-btn border border-white/35 bg-white/10 px-8 text-white hover:border-white hover:bg-white/20"
|
||||
>
|
||||
{{ hero.secondaryCta.label }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="mt-6" data-hero-reveal>
|
||||
<SectionRouteLinks :routes="hero.paths" light />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden gap-4 lg:grid" data-hero-reveal>
|
||||
<div class="rounded-[28px] border border-white/10 bg-white/10 p-6 backdrop-blur">
|
||||
<p class="text-xs uppercase tracking-[0.26em] text-white/55">Growth Signal</p>
|
||||
<div class="mt-6 grid gap-4 sm:grid-cols-3 lg:grid-cols-1">
|
||||
<div class="rounded-2xl border border-white/10 bg-black/10 p-4">
|
||||
<p class="text-sm text-white/65">真人内容网络</p>
|
||||
<p class="mt-2 text-[28px] font-semibold text-white">50,000+</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-white/10 bg-black/10 p-4">
|
||||
<p class="text-sm text-white/65">官媒资源矩阵</p>
|
||||
<p class="mt-2 text-[28px] font-semibold text-white">5,000+</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-white/10 bg-black/10 p-4">
|
||||
<p class="text-sm text-white/65">标准交付流程</p>
|
||||
<p class="mt-2 text-[28px] font-semibold text-white">5 Steps</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,69 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "service",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-white py-16 md:py-20">
|
||||
<div class="container-gm grid gap-10 lg:grid-cols-[0.75fr_1.25fr]">
|
||||
<aside class="lg:sticky lg:top-28 lg:self-start" data-reveal>
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">{{ type }}</p>
|
||||
<h1 class="mt-4 text-balance text-[34px] font-semibold leading-tight text-[#262626] md:text-[42px]">
|
||||
{{ item.title }}
|
||||
</h1>
|
||||
<p class="mt-5 text-base leading-8 text-[#666]">{{ item.summary || item.excerpt }}</p>
|
||||
<p v-if="item.path" class="mt-5 font-mono text-xs text-[#999]">
|
||||
页面路径 {{ item.path }}
|
||||
</p>
|
||||
<NuxtLink to="#contact" class="gm-btn gm-btn-primary mt-8">立即免费咨询</NuxtLink>
|
||||
</aside>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div v-if="item.image" class="overflow-hidden rounded-lg border border-gm-border" data-reveal>
|
||||
<img :src="item.image" :alt="item.title" class="h-[320px] w-full object-cover" data-parallax="24" />
|
||||
</div>
|
||||
|
||||
<div v-if="item.metrics?.length" class="grid gap-4 md:grid-cols-2" data-stagger>
|
||||
<div v-for="metric in item.metrics" :key="metric" class="rounded-lg border border-gm-border bg-gm-light p-5" data-stagger-item>
|
||||
<p class="text-sm font-medium text-[#262626]">{{ metric }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.outcomes?.length" class="grid gap-4 md:grid-cols-2" data-stagger>
|
||||
<div v-for="outcome in item.outcomes" :key="outcome" class="rounded-lg border border-gm-border bg-gm-light p-5" data-stagger-item>
|
||||
<p class="text-sm font-medium text-[#262626]">{{ outcome }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.content?.length" class="rounded-lg border border-gm-border p-6" data-reveal>
|
||||
<p v-for="paragraph in item.content" :key="paragraph" class="mb-4 text-base leading-8 text-[#666] last:mb-0">
|
||||
{{ paragraph }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="item.challenge || item.solution || item.result" class="grid gap-4" data-stagger>
|
||||
<article v-if="item.challenge" class="rounded-lg border border-gm-border p-6" data-stagger-item>
|
||||
<h2 class="text-[20px] font-semibold text-[#262626]">项目挑战</h2>
|
||||
<p class="mt-3 text-sm leading-7 text-[#666]">{{ item.challenge }}</p>
|
||||
</article>
|
||||
<article v-if="item.solution" class="rounded-lg border border-gm-border p-6" data-stagger-item>
|
||||
<h2 class="text-[20px] font-semibold text-[#262626]">解决方案</h2>
|
||||
<p class="mt-3 text-sm leading-7 text-[#666]">{{ item.solution }}</p>
|
||||
</article>
|
||||
<article v-if="item.result" class="rounded-lg border border-gm-border p-6" data-stagger-item>
|
||||
<h2 class="text-[20px] font-semibold text-[#262626]">落地结果</h2>
|
||||
<p class="mt-3 text-sm leading-7 text-[#666]">{{ item.result }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup>
|
||||
import { geoLoopModel } from "~/data/home";
|
||||
|
||||
defineProps({
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => geoLoopModel,
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "model",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-[#f4f4f4] py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="grid gap-10 lg:grid-cols-[0.82fr_1.18fr]">
|
||||
<div data-reveal>
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">GEO Model</p>
|
||||
<h2 class="section-title mt-4">{{ model.title }}</h2>
|
||||
<p class="mt-5 text-base leading-8 text-[#666]">{{ model.description }}</p>
|
||||
<div class="mt-6">
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
<NuxtLink :to="model.cta.href" class="gm-btn gm-btn-primary mt-8">
|
||||
{{ model.cta.label }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-5 md:grid-cols-2" data-stagger data-geo-loop>
|
||||
<article
|
||||
v-for="loop in model.loops"
|
||||
:key="loop.title"
|
||||
class="rounded-lg border border-gm-border bg-white p-6 shadow-soft"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<p class="text-sm font-medium text-gm-green">{{ loop.title }}</p>
|
||||
<p class="mt-3 text-[32px] font-semibold leading-none text-[#262626]">{{ loop.value }}</p>
|
||||
<p class="mt-4 text-sm leading-7 text-[#666]">{{ loop.description }}</p>
|
||||
<div class="mt-6 space-y-3">
|
||||
<div
|
||||
v-for="node in loop.nodes"
|
||||
:key="node"
|
||||
class="flex items-center gap-3 rounded border border-gm-border bg-white px-4 py-3 text-sm text-[#555]"
|
||||
data-geo-node
|
||||
>
|
||||
<span class="size-2 shrink-0 rounded-full bg-gm-green" />
|
||||
<span>{{ node }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,49 @@
|
||||
<script setup>
|
||||
import { homeHero as defaultHero } from "~/data/home";
|
||||
|
||||
defineProps({
|
||||
hero: {
|
||||
type: Object,
|
||||
default: () => defaultHero,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="relative flex min-h-[calc(100vh-var(--page-offset,134px)-40px)] items-center overflow-hidden bg-black pb-14"
|
||||
:style="{ backgroundImage: `url(${hero.backgroundImage})` }"
|
||||
>
|
||||
<div class="absolute inset-0 bg-black/45" />
|
||||
<div class="absolute inset-x-0 bottom-0 z-10 h-28 bg-gradient-to-t from-white to-transparent" />
|
||||
<div class="container-gm relative z-10 py-20 text-center text-white">
|
||||
<p class="text-sm uppercase tracking-[0.35em] text-white/70" data-hero-reveal>
|
||||
{{ hero.eyebrow }}
|
||||
</p>
|
||||
<h1 class="mx-auto mt-6 max-w-4xl text-balance text-4xl font-semibold leading-tight md:text-6xl">
|
||||
<span data-hero-reveal>{{ hero.title }}</span>
|
||||
</h1>
|
||||
<p class="mx-auto mt-6 max-w-2xl text-balance text-base text-white/80 md:text-lg" data-hero-reveal>
|
||||
{{ hero.description }}
|
||||
</p>
|
||||
<div class="mt-10 flex flex-wrap items-center justify-center gap-4" data-hero-reveal>
|
||||
<NuxtLink :to="hero.primaryCta.href" class="gm-btn bg-gm-green px-8 text-white hover:scale-95 hover:bg-gm-green-dark">
|
||||
{{ hero.primaryCta.label }}
|
||||
</NuxtLink>
|
||||
<NuxtLink :to="hero.secondaryCta.href" class="gm-btn border border-white/35 bg-white/10 px-8 text-white hover:border-white hover:bg-white/20">
|
||||
{{ hero.secondaryCta.label }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="mx-auto mt-14 grid max-w-3xl gap-3 rounded-3xl border border-white/15 bg-white/10 p-3 backdrop-blur md:grid-cols-3" data-hero-reveal>
|
||||
<div
|
||||
v-for="metric in hero.metrics"
|
||||
:key="metric.label"
|
||||
class="rounded-2xl bg-black/20 px-4 py-4"
|
||||
>
|
||||
<p class="text-2xl font-semibold text-white">{{ metric.value }}</p>
|
||||
<p class="mt-1 text-xs text-white/65">{{ metric.label }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,128 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { insightCategories, insights as defaultInsights } from "~/data/insights";
|
||||
|
||||
const props = defineProps({
|
||||
insights: {
|
||||
type: Array,
|
||||
default: () => defaultInsights,
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "insights",
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const activeCategory = ref("全部");
|
||||
|
||||
const filteredInsights = computed(() =>
|
||||
activeCategory.value === "全部"
|
||||
? props.insights
|
||||
: props.insights.filter((item) => item.category === activeCategory.value),
|
||||
);
|
||||
|
||||
const featured = computed(() => props.insights.find((item) => item.featured));
|
||||
|
||||
watch(
|
||||
() => route.query.category,
|
||||
(category) => {
|
||||
activeCategory.value =
|
||||
typeof category === "string" && insightCategories.includes(category)
|
||||
? category
|
||||
: "全部";
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-white py-20">
|
||||
<div class="container-gm">
|
||||
<div class="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#a1a1a1]">Insights</p>
|
||||
<h2 class="section-title mt-4">文章列表、热门推荐与分类筛选</h2>
|
||||
</div>
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
|
||||
<article
|
||||
v-if="featured"
|
||||
class="grid overflow-hidden rounded-lg border border-gm-border bg-[#f7f8f7] lg:grid-cols-[1.05fr_0.95fr]"
|
||||
data-reveal
|
||||
>
|
||||
<div class="p-8 md:p-12">
|
||||
<p class="text-sm font-medium text-gm-green">精选观点</p>
|
||||
<h2 class="mt-4 text-balance text-3xl font-semibold leading-tight text-[#262626] md:text-[42px]">
|
||||
{{ featured.title }}
|
||||
</h2>
|
||||
<p class="mt-5 max-w-2xl text-base leading-8 text-[#777]">
|
||||
{{ featured.excerpt }}
|
||||
</p>
|
||||
<p class="mt-8 text-sm text-[#999]">{{ featured.date }} · {{ featured.category }}</p>
|
||||
<p class="mt-3 font-mono text-[11px] text-[#999]">
|
||||
内容路径 {{ featured.path }}
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
:src="featured.image"
|
||||
:alt="featured.title"
|
||||
class="h-full min-h-[320px] w-full object-cover"
|
||||
/>
|
||||
</article>
|
||||
|
||||
<div class="mt-12 flex flex-wrap gap-3" data-reveal>
|
||||
<button
|
||||
v-for="category in insightCategories"
|
||||
:key="category"
|
||||
type="button"
|
||||
class="rounded-full border px-6 py-2 text-sm transition-all duration-300"
|
||||
:class="
|
||||
activeCategory === category
|
||||
? 'border-gm-green bg-gm-green text-white'
|
||||
: 'border-gm-border bg-white text-[#666] hover:border-[#d5d5d5]'
|
||||
"
|
||||
@click="activeCategory = category"
|
||||
>
|
||||
{{ category }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-6 md:grid-cols-2 xl:grid-cols-3" data-stagger>
|
||||
<NuxtLink
|
||||
v-for="item in filteredInsights"
|
||||
:key="item.title"
|
||||
:to="`/insights/${item.slug}`"
|
||||
class="overflow-hidden rounded-lg border border-gm-border bg-white shadow-sm transition hover:shadow-hover"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<div class="h-52 overflow-hidden bg-gm-light">
|
||||
<img
|
||||
:src="item.image"
|
||||
:alt="item.title"
|
||||
class="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<p class="text-xs font-medium text-gm-green">{{ item.category }}</p>
|
||||
<h3 class="mt-3 text-lg font-semibold leading-7 text-[#262626]">
|
||||
{{ item.title }}
|
||||
</h3>
|
||||
<p class="mt-3 text-sm leading-7 text-[#777]">{{ item.excerpt }}</p>
|
||||
<p class="mt-5 text-xs text-[#9b9b9b]">{{ item.date }}</p>
|
||||
<p class="mt-3 font-mono text-[11px] text-[#999]">
|
||||
内容路径 {{ item.path }}
|
||||
</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
eyebrow: {
|
||||
type: String,
|
||||
default: "Proof",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
metrics: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
logos: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "clients",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-white py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">{{ eyebrow }}</p>
|
||||
<h2 class="section-title mt-4">{{ title }}</h2>
|
||||
<p v-if="description" class="mt-4 text-base leading-8 text-[#666]">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4" data-stagger>
|
||||
<AnimatedStat
|
||||
v-for="metric in metrics"
|
||||
:key="metric.label"
|
||||
:label="metric.label"
|
||||
:value="metric.value"
|
||||
:suffix="metric.suffix"
|
||||
data-stagger-item
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="logos.length" class="mt-10">
|
||||
<p class="mb-4 text-xs uppercase tracking-[0.28em] text-[#999]">覆盖行业</p>
|
||||
<div class="grid grid-cols-2 gap-3 md:grid-cols-4 xl:grid-cols-6" data-stagger>
|
||||
<div
|
||||
v-for="logo in logos"
|
||||
:key="logo"
|
||||
class="flex h-12 items-center justify-center rounded border border-gm-border bg-gm-light text-sm text-[#666]"
|
||||
data-stagger-item
|
||||
>
|
||||
{{ logo }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,56 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
eyebrow: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
default: "/images/banner-bg.jpg",
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="relative overflow-hidden bg-black bg-cover bg-center text-white"
|
||||
:style="{ backgroundImage: `url(${image})` }"
|
||||
>
|
||||
<div class="absolute inset-0 bg-black/55" />
|
||||
<div class="absolute inset-x-0 bottom-0 h-24 bg-gradient-to-t from-white to-transparent" />
|
||||
<div class="container-gm relative z-10 py-24 md:py-32">
|
||||
<p
|
||||
v-if="eyebrow"
|
||||
class="text-sm uppercase tracking-[0.34em] text-white/65"
|
||||
data-hero-reveal
|
||||
>
|
||||
{{ eyebrow }}
|
||||
</p>
|
||||
<h1 class="mt-5 max-w-4xl text-balance text-[34px] font-semibold leading-tight md:text-[42px]" data-hero-reveal>
|
||||
{{ title }}
|
||||
</h1>
|
||||
<p
|
||||
v-if="description"
|
||||
class="mt-6 max-w-2xl text-base leading-8 text-white/78 md:text-lg"
|
||||
data-hero-reveal
|
||||
>
|
||||
{{ description }}
|
||||
</p>
|
||||
<div v-if="routes.length" class="mt-8" data-hero-reveal>
|
||||
<SectionRouteLinks :routes="routes" light />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,56 @@
|
||||
<script setup>
|
||||
import { processSteps } from "~/data/home";
|
||||
|
||||
defineProps({
|
||||
steps: {
|
||||
type: Array,
|
||||
default: () => processSteps,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "5 步标准化交付流程,透明可控效果可追溯",
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "标准化流程加透明化交付,让服务可控、效果清晰可见。",
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "process",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-white py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">Process</p>
|
||||
<h2 class="section-title mt-4">{{ title }}</h2>
|
||||
<p class="mt-4 text-base leading-8 text-[#666]">{{ description }}</p>
|
||||
</div>
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
|
||||
<div class="relative mt-10 grid gap-4 md:grid-cols-5" data-stagger data-process-line>
|
||||
<div class="absolute left-0 top-8 hidden h-px w-full bg-gm-border md:block" />
|
||||
<div class="absolute left-0 top-8 hidden h-px w-0 bg-gm-green md:block" data-flow-line />
|
||||
<article
|
||||
v-for="step in steps"
|
||||
:key="step.step"
|
||||
class="relative rounded-lg border border-gm-border bg-white p-5 shadow-sm"
|
||||
data-stagger-item
|
||||
>
|
||||
<p class="text-[24px] font-semibold text-gm-green">{{ step.step }}</p>
|
||||
<h3 class="mt-4 text-[18px] font-semibold text-[#262626]">{{ step.title }}</h3>
|
||||
<p class="mt-3 text-sm leading-7 text-[#666]">{{ step.text }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { roiStats as defaultStats } from "~/data/home";
|
||||
|
||||
defineProps({
|
||||
stats: {
|
||||
type: Array,
|
||||
default: () => defaultStats,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "我们交付投资回报率",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-white py-20">
|
||||
<div class="container-gm">
|
||||
<div class="max-w-2xl" data-reveal>
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#a1a1a1]">ROI</p>
|
||||
<h2 class="section-title mt-4">{{ title }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 grid gap-6 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<AnimatedStat
|
||||
v-for="stat in stats"
|
||||
:key="stat.label + stat.value"
|
||||
:label="stat.label"
|
||||
:value="stat.value"
|
||||
:suffix="stat.suffix"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,94 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
service: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-white py-20">
|
||||
<div class="container-gm grid gap-12 lg:grid-cols-[0.85fr_1.15fr]">
|
||||
<div class="lg:sticky lg:top-28 lg:self-start" data-reveal>
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#a1a1a1]">{{ service.tier }}</p>
|
||||
<h1 class="mt-4 text-balance text-4xl font-semibold leading-tight text-[#262626] md:text-[56px]">
|
||||
{{ service.title }}
|
||||
</h1>
|
||||
<p class="mt-5 text-base leading-8 text-[#777]">{{ service.summary }}</p>
|
||||
<NuxtLink :to="service.cta.href" class="gm-btn gm-btn-primary mt-8 px-8">
|
||||
{{ service.cta.label }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="overflow-hidden rounded-[28px] border border-gm-border" data-reveal>
|
||||
<img
|
||||
:src="service.image"
|
||||
:alt="service.title"
|
||||
class="h-[340px] w-full object-cover"
|
||||
data-parallax="30"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 md:grid-cols-3" data-stagger>
|
||||
<article
|
||||
v-for="metric in service.metrics"
|
||||
:key="metric.label"
|
||||
class="rounded-[24px] border border-gm-border bg-[#f7f8f7] p-6"
|
||||
data-stagger-item
|
||||
>
|
||||
<p class="text-3xl font-semibold text-gm-green">{{ metric.value }}</p>
|
||||
<p class="mt-2 text-sm text-[#777]">{{ metric.label }}</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 md:grid-cols-2" data-stagger>
|
||||
<article class="rounded-[24px] border border-gm-border p-6" data-stagger-item>
|
||||
<h2 class="text-xl font-semibold text-[#262626]">适用场景</h2>
|
||||
<ul class="mt-5 space-y-3">
|
||||
<li
|
||||
v-for="pain in service.pains"
|
||||
:key="pain"
|
||||
class="flex gap-3 text-sm leading-7 text-[#666]"
|
||||
>
|
||||
<span class="mt-2 size-1.5 shrink-0 rounded-full bg-gm-green" />
|
||||
<span>{{ pain }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="rounded-[24px] border border-gm-border p-6" data-stagger-item>
|
||||
<h2 class="text-xl font-semibold text-[#262626]">核心交付物</h2>
|
||||
<ul class="mt-5 space-y-3">
|
||||
<li
|
||||
v-for="item in service.deliverables"
|
||||
:key="item"
|
||||
class="flex gap-3 text-sm leading-7 text-[#666]"
|
||||
>
|
||||
<span class="mt-2 size-1.5 shrink-0 rounded-full bg-gm-green" />
|
||||
<span>{{ item }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[24px] border border-gm-border bg-white p-6" data-reveal>
|
||||
<h2 class="text-xl font-semibold text-[#262626]">常见问题</h2>
|
||||
<div class="mt-5 divide-y divide-gm-border">
|
||||
<details
|
||||
v-for="faq in service.faqs"
|
||||
:key="faq.question"
|
||||
class="group py-4"
|
||||
>
|
||||
<summary class="cursor-pointer list-none text-base font-medium text-[#262626]">
|
||||
{{ faq.question }}
|
||||
</summary>
|
||||
<p class="mt-3 text-sm leading-7 text-[#777]">{{ faq.answer }}</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,92 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
eyebrow: {
|
||||
type: String,
|
||||
default: "Three Directions",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
services: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "service-directions",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-[#f4f4f4] py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">{{ eyebrow }}</p>
|
||||
<h2 class="section-title mt-4">{{ title }}</h2>
|
||||
<p v-if="description" class="mt-4 text-base leading-8 text-[#666]">{{ description }}</p>
|
||||
</div>
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-5 lg:grid-cols-3" data-stagger>
|
||||
<NuxtLink
|
||||
v-for="service in services"
|
||||
:key="service.slug"
|
||||
:to="service.href"
|
||||
class="group rounded-lg border border-gm-border bg-white p-6 shadow-sm transition hover:shadow-hover"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.28em] text-gm-green">{{ service.tags.join(" / ") }}</p>
|
||||
<h3 class="mt-4 text-[22px] font-semibold text-[#262626]">{{ service.title }}</h3>
|
||||
</div>
|
||||
<span class="rounded-full bg-gm-light px-3 py-1 text-xs text-[#666]">{{ service.metrics[0].value }}</span>
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-sm leading-7 text-[#666]">{{ service.summary }}</p>
|
||||
|
||||
<div class="mt-5 flex flex-wrap gap-2">
|
||||
<span
|
||||
v-for="client in service.fitClients"
|
||||
:key="client"
|
||||
class="rounded-full border border-gm-border px-3 py-1 text-xs text-[#666]"
|
||||
>
|
||||
{{ client }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid gap-3">
|
||||
<div
|
||||
v-for="item in service.deliverables.slice(0, 3)"
|
||||
:key="item"
|
||||
class="flex gap-3 text-sm text-[#555]"
|
||||
>
|
||||
<span class="mt-2 size-1.5 shrink-0 rounded-full bg-gm-green" />
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-6 font-mono text-[11px] text-[#999]">
|
||||
服务路径 {{ service.path }}
|
||||
</p>
|
||||
<span class="mt-4 inline-flex items-center gap-2 text-sm font-medium text-gm-green">
|
||||
查看服务详情 <span aria-hidden="true">→</span>
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
import { serviceOverview } from "~/data/services";
|
||||
|
||||
defineProps({
|
||||
hero: {
|
||||
type: Object,
|
||||
default: () => serviceOverview.hero,
|
||||
},
|
||||
services: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-white py-20">
|
||||
<div class="container-gm grid items-start gap-12 lg:grid-cols-[1fr_508px]">
|
||||
<div class="max-w-[720px]">
|
||||
<p class="text-[18px] uppercase leading-none text-[#b8b8b8]" data-reveal>
|
||||
{{ hero.eyebrow }}
|
||||
</p>
|
||||
<h1 class="mt-4 text-balance text-[42px] font-medium leading-[1.18] text-[#262626] md:text-[52px]" data-reveal>
|
||||
{{ hero.title }}
|
||||
</h1>
|
||||
<p class="mt-5 max-w-2xl text-base leading-8 text-[#777]" data-reveal>
|
||||
{{ hero.description }}
|
||||
</p>
|
||||
|
||||
<div class="mt-12 space-y-8" data-stagger>
|
||||
<NuxtLink
|
||||
v-for="service in services"
|
||||
:key="service.href"
|
||||
:to="service.href"
|
||||
class="block"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<p class="mb-4 text-[18px] font-medium text-[#12d12c]">
|
||||
{{ service.tier }}
|
||||
</p>
|
||||
<h2 class="text-[28px] font-medium leading-tight text-[#262626] md:text-[34px]">
|
||||
{{ service.title }}
|
||||
</h2>
|
||||
<p class="mt-4 max-w-[680px] text-base leading-8 text-[#999]">
|
||||
{{ service.summary }}
|
||||
</p>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative hidden justify-center lg:flex" data-reveal>
|
||||
<img
|
||||
:src="hero.image"
|
||||
alt="增长超人服务能力展示"
|
||||
class="h-auto w-[508px] select-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "service-model",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-white py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">{{ model.eyebrow }}</p>
|
||||
<h2 class="section-title mt-4">{{ model.title }}</h2>
|
||||
<p class="mt-4 text-base leading-8 text-[#666]">{{ model.description }}</p>
|
||||
</div>
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-5 lg:grid-cols-3" data-stagger>
|
||||
<article
|
||||
v-for="stage in model.stages"
|
||||
:key="stage.phase"
|
||||
class="rounded-lg border border-gm-border bg-[#f7f8f7] p-6"
|
||||
data-stagger-item
|
||||
>
|
||||
<p class="text-[28px] font-semibold text-gm-green">{{ stage.phase }}</p>
|
||||
<h3 class="mt-4 text-[22px] font-semibold text-[#262626]">{{ stage.title }}</h3>
|
||||
<p class="mt-3 text-sm leading-7 text-[#666]">{{ stage.summary }}</p>
|
||||
|
||||
<div class="mt-5 space-y-3">
|
||||
<div
|
||||
v-for="point in stage.points"
|
||||
:key="point"
|
||||
class="flex gap-3 text-sm text-[#555]"
|
||||
>
|
||||
<span class="mt-2 size-1.5 shrink-0 rounded-full bg-gm-green" />
|
||||
<span>{{ point }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NuxtLink :to="stage.href" class="mt-6 inline-flex items-center gap-2 text-sm font-medium text-gm-green">
|
||||
{{ stage.service }}
|
||||
<span aria-hidden="true">→</span>
|
||||
</NuxtLink>
|
||||
<p class="mt-4 font-mono text-[11px] text-[#999]">
|
||||
模型对应路径 {{ stage.path }}
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,62 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
overview: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "service-overview",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-white py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">{{ overview.eyebrow }}</p>
|
||||
<h2 class="section-title mt-4">{{ overview.title }}</h2>
|
||||
<p class="mt-4 text-base leading-8 text-[#666]">{{ overview.description }}</p>
|
||||
</div>
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-6 lg:grid-cols-[1.15fr_0.85fr]">
|
||||
<div class="rounded-lg border border-gm-border bg-[#f7f8f7] p-6 md:p-8" data-stagger>
|
||||
<p class="text-xs uppercase tracking-[0.28em] text-[#999]">Service Value</p>
|
||||
<div class="mt-6 space-y-4">
|
||||
<div
|
||||
v-for="item in overview.highlights"
|
||||
:key="item"
|
||||
class="flex gap-3 rounded-lg border border-gm-border bg-white px-4 py-4 text-sm leading-7 text-[#555]"
|
||||
data-stagger-item
|
||||
>
|
||||
<span class="mt-2 size-2 shrink-0 rounded-full bg-gm-green" />
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-gm-border bg-white p-6 md:p-8" data-reveal>
|
||||
<p class="text-xs uppercase tracking-[0.28em] text-[#999]">Fit Clients</p>
|
||||
<h3 class="mt-4 text-[22px] font-semibold text-[#262626]">适用客户场景</h3>
|
||||
<div class="mt-6 flex flex-wrap gap-3">
|
||||
<span
|
||||
v-for="item in overview.audiences"
|
||||
:key="item"
|
||||
class="rounded-full border border-gm-border bg-gm-light px-4 py-2 text-sm text-[#555]"
|
||||
>
|
||||
{{ item }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script setup>
|
||||
import { serviceOverview } from "~/data/services";
|
||||
|
||||
defineProps({
|
||||
problem: {
|
||||
type: Object,
|
||||
default: () => serviceOverview.problem,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="bg-top bg-no-repeat py-6 md:py-16"
|
||||
:style="{ backgroundImage: `url(${problem.backgroundImage})` }"
|
||||
>
|
||||
<div class="container-gm">
|
||||
<div class="mx-auto max-w-[760px] text-center" data-reveal>
|
||||
<h2 class="section-title text-balance">
|
||||
企业的营销数字化面临的
|
||||
<span class="text-gm-green">三个问题</span>
|
||||
,增长超人将如何帮您解决?
|
||||
</h2>
|
||||
<div class="mx-auto mt-7 flex size-12 items-center justify-center rounded-full border border-[#b7f7c2] text-gm-green shadow-[0_0_0_8px_rgba(27,214,41,0.08)]">
|
||||
↓
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="mt-14 grid gap-10 rounded-[20px] bg-white px-6 py-8 shadow-soft lg:mt-[92px] lg:grid-cols-[1.05fr_0.95fr] lg:px-12 lg:py-10" data-stagger>
|
||||
<div data-stagger-item>
|
||||
<span class="rounded-full bg-gm-green px-4 py-1 text-xs font-medium text-white">
|
||||
{{ problem.problemLabel }}
|
||||
</span>
|
||||
<h3 class="mt-8 text-[24px] font-medium leading-[1.5] text-[#7f7f7f] md:text-[30px]">
|
||||
{{ problem.problem }}
|
||||
</h3>
|
||||
|
||||
<p class="mt-9 text-sm text-[#999]">{{ problem.solutionTitle }}</p>
|
||||
<p class="mt-4 text-[24px] font-medium leading-[1.5] text-[#8f8f8f] md:text-[30px]">
|
||||
{{ problem.solution }}
|
||||
</p>
|
||||
|
||||
<div class="mt-16 space-y-5">
|
||||
<p class="text-sm text-[#999]">网站开发服务</p>
|
||||
<NuxtLink :to="problem.solutionLink.href" class="inline-flex items-center gap-2 text-sm font-medium text-gm-green">
|
||||
{{ problem.solutionLink.label }}
|
||||
<span aria-hidden="true">→</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-stagger-item>
|
||||
<p class="mb-5 text-sm text-[#999]">相关案例</p>
|
||||
<div class="grid grid-cols-2 border-l border-t border-[#edf0ee] md:grid-cols-3">
|
||||
<div
|
||||
v-for="item in problem.cases"
|
||||
:key="item"
|
||||
class="flex min-h-[96px] items-center justify-center border-b border-r border-[#edf0ee] px-3 text-center text-xs leading-relaxed text-[#828282]"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mb-5 mt-10 text-sm text-[#999]">相关洞察</p>
|
||||
<ul class="space-y-3">
|
||||
<li
|
||||
v-for="(item, index) in problem.insights"
|
||||
:key="item"
|
||||
class="flex gap-2 text-sm leading-relaxed text-[#555]"
|
||||
>
|
||||
<span class="mt-2 size-1 shrink-0 rounded-full bg-gm-green" />
|
||||
<span>
|
||||
{{ item }}
|
||||
<span
|
||||
v-if="index === 0"
|
||||
class="ml-2 rounded-full bg-gm-green px-2 py-0.5 text-[10px] text-white"
|
||||
>
|
||||
HOT
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
steps: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "交付流程",
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "从诊断到上线,再到持续复盘,把增长动作落到可执行的节奏里。",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-white py-20">
|
||||
<div class="container-gm">
|
||||
<div class="max-w-2xl" data-reveal>
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#a1a1a1]">Process</p>
|
||||
<h2 class="section-title mt-4">{{ title }}</h2>
|
||||
<p class="mt-4 text-base leading-8 text-[#777]">{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 grid gap-5 md:grid-cols-2 xl:grid-cols-4" data-stagger>
|
||||
<article
|
||||
v-for="step in steps"
|
||||
:key="step.step || step"
|
||||
class="rounded-[24px] border border-gm-border bg-white p-6 shadow-sm"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<p class="text-[42px] font-semibold leading-none text-gm-green">
|
||||
{{ step.step || String(steps.indexOf(step) + 1).padStart(2, "0") }}
|
||||
</p>
|
||||
<h3 class="mt-5 text-xl font-semibold text-[#262626]">
|
||||
{{ step.title || step }}
|
||||
</h3>
|
||||
<p v-if="step.text" class="mt-3 text-sm leading-7 text-[#777]">
|
||||
{{ step.text }}
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
results: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "service-results",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-[#f4f4f4] py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">{{ results.eyebrow }}</p>
|
||||
<h2 class="section-title mt-4">{{ results.title }}</h2>
|
||||
<p class="mt-4 text-base leading-8 text-[#666]">{{ results.description }}</p>
|
||||
</div>
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-5 lg:grid-cols-[0.9fr_1.1fr]">
|
||||
<div class="grid gap-4" data-stagger>
|
||||
<article
|
||||
v-for="item in results.proofs"
|
||||
:key="item.label"
|
||||
class="rounded-lg border border-gm-border bg-white p-6"
|
||||
data-stagger-item
|
||||
>
|
||||
<p class="text-[34px] font-semibold leading-none text-gm-green">{{ item.value }}</p>
|
||||
<h3 class="mt-4 text-[18px] font-semibold text-[#262626]">{{ item.label }}</h3>
|
||||
<p class="mt-3 text-sm leading-7 text-[#666]">{{ item.note }}</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-5 md:grid-cols-2" data-stagger>
|
||||
<NuxtLink
|
||||
v-for="item in results.cases"
|
||||
:key="item.href"
|
||||
:to="item.href"
|
||||
class="rounded-lg border border-gm-border bg-white p-6 shadow-sm transition hover:shadow-hover"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<p class="text-xs uppercase tracking-[0.28em] text-gm-green">Case Proof</p>
|
||||
<h3 class="mt-4 text-[20px] font-semibold leading-7 text-[#262626]">{{ item.title }}</h3>
|
||||
<p class="mt-4 text-sm leading-7 text-[#666]">{{ item.summary }}</p>
|
||||
<p class="mt-6 font-mono text-[11px] text-[#999]">
|
||||
案例路径 {{ item.path }}
|
||||
</p>
|
||||
<span class="mt-4 inline-flex items-center gap-2 text-sm font-medium text-gm-green">
|
||||
查看案例详情 <span aria-hidden="true">→</span>
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
import { serviceItems } from "~/data/services";
|
||||
|
||||
defineProps({
|
||||
services: {
|
||||
type: Array,
|
||||
default: () => serviceItems,
|
||||
},
|
||||
eyebrow: {
|
||||
type: String,
|
||||
default: "Method",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "基于高楼营销法则",
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "提供全链路增长服务",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-white py-20" id="services">
|
||||
<div class="container-gm grid gap-10 lg:grid-cols-[0.95fr_1.05fr]">
|
||||
<div class="lg:sticky lg:top-28 lg:self-start" data-reveal>
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#a1a1a1]">{{ eyebrow }}</p>
|
||||
<h2 class="mt-4 text-[34px] font-semibold leading-tight text-[#262626] md:text-[42px]">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<p class="mt-4 text-lg text-[#666]">{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8" data-stagger>
|
||||
<NuxtLink
|
||||
v-for="(service, index) in services"
|
||||
:key="service.slug"
|
||||
:to="service.href"
|
||||
class="block border-b border-gm-border pb-8 last:border-b-0"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<p class="text-[44px] font-semibold leading-none text-gm-green">
|
||||
{{ String(index + 1).padStart(2, "0") }}
|
||||
</p>
|
||||
<h3 class="mt-4 text-[28px] font-semibold text-[#262626]">
|
||||
{{ service.title }}
|
||||
</h3>
|
||||
<p class="mt-2 text-base text-[#666]">{{ service.tier }}</p>
|
||||
<p class="mt-4 max-w-2xl text-sm leading-7 text-[#777]">
|
||||
{{ service.summary }}
|
||||
</p>
|
||||
<span class="mt-5 inline-flex items-center gap-2 text-sm font-medium text-gm-green">
|
||||
查看服务详情 <span aria-hidden="true">→</span>
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,77 @@
|
||||
<script setup>
|
||||
import { solutions as defaultSolutions } from "~/data/solutions";
|
||||
|
||||
defineProps({
|
||||
solutions: {
|
||||
type: Array,
|
||||
default: () => defaultSolutions,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "深耕 12+ 行业,定制专属 GEO 增长方案",
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "让不同行业客户快速找到与自身相关的增长方案,提升业务匹配度感知。",
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
preview: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "solutions",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-[#f4f4f4] py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">Solutions</p>
|
||||
<h2 class="section-title mt-4">{{ title }}</h2>
|
||||
<p class="mt-4 text-base leading-8 text-[#666]">{{ description }}</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-3 md:items-end">
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
<NuxtLink v-if="preview" to="/solutions" class="text-sm font-medium text-gm-green">
|
||||
获取我的行业专属方案 →
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-5 md:grid-cols-2 xl:grid-cols-3" data-stagger>
|
||||
<NuxtLink
|
||||
v-for="item in solutions"
|
||||
:key="item.slug"
|
||||
:to="item.href"
|
||||
class="group overflow-hidden rounded-lg border border-gm-border bg-white shadow-sm"
|
||||
data-stagger-item
|
||||
data-hover-card
|
||||
>
|
||||
<div class="h-44 overflow-hidden bg-gm-light">
|
||||
<img :src="item.image" :alt="item.title" class="h-full w-full object-cover transition duration-500 group-hover:scale-105" loading="lazy" />
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<h3 class="text-[20px] font-semibold text-[#262626]">{{ item.title }}</h3>
|
||||
<p class="mt-3 text-sm leading-7 text-[#666]">{{ item.summary }}</p>
|
||||
<div class="mt-5 flex flex-wrap gap-2">
|
||||
<span v-for="tag in item.tags" :key="tag" class="rounded bg-gm-light px-3 py-1 text-xs text-[#666]">
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-5 font-mono text-[11px] text-[#999]">
|
||||
方案路径 {{ item.path || item.href }}
|
||||
</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,89 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
mapping: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "solution-mapping",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :id="sectionId" class="bg-white py-16 md:py-20">
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#999]">{{ mapping.eyebrow }}</p>
|
||||
<h2 class="section-title mt-4">{{ mapping.title }}</h2>
|
||||
<p class="mt-4 text-base leading-8 text-[#666]">{{ mapping.description }}</p>
|
||||
</div>
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid gap-5 md:grid-cols-2" data-stagger>
|
||||
<article
|
||||
v-for="item in mapping.items"
|
||||
:key="item.industry"
|
||||
class="rounded-lg border border-gm-border bg-[#f7f8f7] p-6"
|
||||
data-stagger-item
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.28em] text-gm-green">Industry</p>
|
||||
<h3 class="mt-3 text-[22px] font-semibold text-[#262626]">{{ item.industry }}</h3>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
<span
|
||||
v-for="tag in item.tags"
|
||||
:key="tag"
|
||||
class="rounded-full border border-gm-border bg-white px-3 py-1 text-xs text-[#666]"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 space-y-4 text-sm leading-7">
|
||||
<div>
|
||||
<p class="font-medium text-[#262626]">行业痛点</p>
|
||||
<p class="mt-1 text-[#666]">{{ item.pain }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-[#262626]">对应方案</p>
|
||||
<p class="mt-1 text-[#666]">{{ item.plan }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-3">
|
||||
<NuxtLink
|
||||
:to="item.solutionHref"
|
||||
class="inline-flex items-center gap-2 rounded-full border border-gm-green px-4 py-2 text-sm font-medium text-gm-green transition hover:bg-gm-green hover:text-white"
|
||||
>
|
||||
查看行业方案
|
||||
<span aria-hidden="true">→</span>
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
:to="item.caseHref"
|
||||
class="inline-flex items-center gap-2 rounded-full border border-gm-border bg-white px-4 py-2 text-sm text-[#555] transition hover:border-gm-green hover:text-gm-green"
|
||||
>
|
||||
查看对应案例
|
||||
<span aria-hidden="true">→</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 space-y-2 font-mono text-[11px] text-[#999]">
|
||||
<p>方案路径 {{ item.solutionPath }}</p>
|
||||
<p>案例路径 {{ item.casePath }}</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,27 @@
|
||||
<script setup>
|
||||
import { aboutPage } from "~/data/about";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-white py-20" id="about">
|
||||
<div class="container-gm grid items-center gap-12 lg:grid-cols-2">
|
||||
<div data-reveal>
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#a1a1a1]">About</p>
|
||||
<h2 class="mt-4 text-balance text-3xl font-semibold leading-snug text-[#262626] md:text-[40px]">
|
||||
{{ aboutPage.hero.title }}
|
||||
</h2>
|
||||
<p class="mt-5 max-w-xl text-base leading-8 text-[#777]">
|
||||
{{ aboutPage.hero.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded-[32px] border border-gm-border shadow-xl" data-reveal>
|
||||
<img
|
||||
:src="aboutPage.hero.image"
|
||||
alt="增长超人团队"
|
||||
class="h-full min-h-[320px] w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,132 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import { testimonials as defaultTestimonials } from "~/data/home";
|
||||
|
||||
const props = defineProps({
|
||||
testimonials: {
|
||||
type: Array,
|
||||
default: () => defaultTestimonials,
|
||||
},
|
||||
routes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
default: "testimonials",
|
||||
},
|
||||
});
|
||||
|
||||
const currentIndex = ref(0);
|
||||
const isPaused = ref(false);
|
||||
let timer;
|
||||
|
||||
const current = computed(() => props.testimonials[currentIndex.value]);
|
||||
|
||||
const goToSlide = (index) => {
|
||||
currentIndex.value = index;
|
||||
};
|
||||
|
||||
const goToPrev = () => {
|
||||
currentIndex.value =
|
||||
currentIndex.value === 0 ? props.testimonials.length - 1 : currentIndex.value - 1;
|
||||
};
|
||||
|
||||
const goToNext = () => {
|
||||
currentIndex.value =
|
||||
currentIndex.value === props.testimonials.length - 1 ? 0 : currentIndex.value + 1;
|
||||
};
|
||||
|
||||
const startAutoplay = () => {
|
||||
timer = window.setInterval(() => {
|
||||
if (!isPaused.value) {
|
||||
goToNext();
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
startAutoplay();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.clearInterval(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
:id="sectionId"
|
||||
class="bg-white py-20"
|
||||
@mouseenter="isPaused = true"
|
||||
@mouseleave="isPaused = false"
|
||||
>
|
||||
<div class="container-gm">
|
||||
<div class="flex flex-col gap-5 md:flex-row md:items-end md:justify-between" data-reveal>
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm uppercase tracking-[0.3em] text-[#a1a1a1]">Voice</p>
|
||||
<h2 class="section-title mt-4">客户真实评价,见证服务品质</h2>
|
||||
<p class="mt-3 text-[#777]">
|
||||
从真实客户视角补充信任背书,进一步强化团队专业度和落地能力。
|
||||
</p>
|
||||
</div>
|
||||
<SectionRouteLinks :routes="routes" />
|
||||
</div>
|
||||
|
||||
<div class="mx-auto mt-12 max-w-3xl text-center" data-reveal>
|
||||
<div class="mx-auto flex h-16 w-16 items-center justify-center rounded-lg bg-gm-light text-xl font-semibold text-gm-green">
|
||||
{{ current?.name.slice(0, 1) }}
|
||||
</div>
|
||||
|
||||
<transition
|
||||
mode="out-in"
|
||||
enter-active-class="transition duration-500 ease-out"
|
||||
enter-from-class="translate-y-3 opacity-0"
|
||||
enter-to-class="translate-y-0 opacity-100"
|
||||
leave-active-class="transition duration-300 ease-in"
|
||||
leave-from-class="translate-y-0 opacity-100"
|
||||
leave-to-class="-translate-y-2 opacity-0"
|
||||
>
|
||||
<div :key="current.id" class="mt-6">
|
||||
<blockquote class="text-balance text-lg italic leading-8 text-[#555] md:text-xl">
|
||||
“{{ current.quote }}”
|
||||
</blockquote>
|
||||
<p class="mt-6 text-base font-semibold text-[#262626]">{{ current.name }}</p>
|
||||
<p class="text-sm text-[#9b9b9b]">{{ current.title }}</p>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex items-center justify-center gap-2">
|
||||
<button
|
||||
v-for="(item, index) in props.testimonials"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
:aria-label="`切换到第 ${index + 1} 条评价`"
|
||||
class="size-3 rounded-full transition-all duration-300"
|
||||
:class="index === currentIndex ? 'bg-gm-green' : 'bg-gray-300'"
|
||||
@click="goToSlide(index)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex items-center justify-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
class="flex size-10 items-center justify-center rounded-full border border-gm-border text-[#666] transition hover:border-gm-green hover:text-gm-green"
|
||||
aria-label="上一条评价"
|
||||
@click="goToPrev"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="flex size-10 items-center justify-center rounded-full border border-gm-border text-[#666] transition hover:border-gm-green hover:text-gm-green"
|
||||
aria-label="下一条评价"
|
||||
@click="goToNext"
|
||||
>
|
||||
→
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user