This commit is contained in:
2026-07-10 18:23:41 +08:00
commit bc9e794a57
80 changed files with 19016 additions and 0 deletions
@@ -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>
+68
View File
@@ -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>
+150
View File
@@ -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>
+49
View File
@@ -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>
+128
View File
@@ -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>
+74
View File
@@ -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>
+56
View File
@@ -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>
+35
View File
@@ -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>
+61
View File
@@ -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>
+27
View File
@@ -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>
+132
View File
@@ -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>