Files
BigStickNewOfficialWebsite-V2/pages/cases/[slug].vue
T

619 lines
15 KiB
Vue
Raw Normal View History

2026-07-16 16:42:45 +08:00
<script setup>
import SiteFooter from '~/components/site/SiteFooter.vue'
2026-07-21 11:09:17 +08:00
import SiteHeader from '~/components/site/SiteHeader.vue'
import UEditor from '~/components/UEditor.vue'
2026-07-16 16:42:45 +08:00
const route = useRoute()
const { openConsultation } = useConsultation()
2026-07-21 11:09:17 +08:00
const { bySortAndTime, fetchPublicContent, normalizeCase } = useOfficialContent()
const slug = String(route.params.slug || '')
const { data: pageData } = await useAsyncData(`public-case-${slug}`, async () => {
const rows = await fetchPublicContent('CASE')
const cases = rows.sort(bySortAndTime).map((item) => normalizeCase(item, new Map()))
return {
cases,
current: cases.find((item) => item.slug === slug) || null
}
})
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
if (!pageData.value?.current) {
2026-07-16 16:42:45 +08:00
throw createError({ statusCode: 404, statusMessage: '案例不存在' })
}
2026-07-21 11:09:17 +08:00
const caseStudy = computed(() => pageData.value.current)
const articleTitle = computed(() => caseStudy.value.seoTitle || caseStudy.value.title)
const articleTag = computed(() => caseStudy.value.industry || '客户案例')
const articleHtml = computed(() => caseStudy.value.contentHtml || '<p>暂无正文内容。</p>')
const caseTags = computed(() =>
[caseStudy.value.industry, caseStudy.value.isFullCase ? '完整案例' : '结果快照'].filter(Boolean)
)
const relatedCases = computed(() =>
(pageData.value?.cases || [])
.filter((item) => item.slug !== slug && item.isRecommended === true)
.slice(0, 4)
2026-07-21 11:09:17 +08:00
)
const recommendedCases = computed(() =>
(pageData.value?.cases || [])
.filter(
(item) => item.slug !== slug && item.isRecommended === true && item.isFullCase && item.slug
)
.slice(0, 5)
)
function caseListLink(item) {
return item.isFullCase && item.slug ? `/cases/${item.slug}` : '/cases'
}
2026-07-21 11:09:17 +08:00
useHead(() => ({
title: `${articleTitle.value}|客户案例`,
meta: [
{
name: 'description',
content:
caseStudy.value.seoDescription ||
caseStudy.value.description ||
caseStudy.value.summary ||
articleTitle.value
2026-07-21 11:09:17 +08:00
},
{ property: 'og:title', content: articleTitle.value },
{ property: 'og:description', content: caseStudy.value.description || caseStudy.value.summary },
{ property: 'og:image', content: caseStudy.value.cover }
]
}))
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
function requestPlanConsultation() {
2026-07-16 16:42:45 +08:00
openConsultation({
2026-07-21 11:09:17 +08:00
title: '方案咨询',
industry: caseStudy.value.industry,
need: articleTitle.value,
source: '客户案例详情页 CTA'
2026-07-16 16:42:45 +08:00
})
}
2026-07-21 11:09:17 +08:00
function escapeHtml(value) {
return String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#039;')
}
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
function renderParagraphs(paragraphs = []) {
return paragraphs.map((paragraph) => `<p>${escapeHtml(paragraph)}</p>`).join('')
}
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
function renderList(items = []) {
if (!items.length) return ''
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>`
}
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
function renderKeyValueList(items = []) {
if (!items.length) return ''
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
return `<ul>${items
.map((item) => `<li><strong>${escapeHtml(item[0])}</strong>${escapeHtml(item[1])}</li>`)
.join('')}</ul>`
}
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
function renderQuestionFramework(items = []) {
if (!items.length) return ''
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
return items
.map(
(item) => `
<h3>${escapeHtml(item.title)}</h3>
<p>${escapeHtml(item.content)}</p>
<blockquote>${escapeHtml(item.goal)}</blockquote>
`
)
.join('')
}
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
function renderPhases(items = []) {
if (!items.length) return ''
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
return items
.map(
(item) => `
<h3>${escapeHtml(item.number)}${escapeHtml(item.title)}</h3>
<p>${escapeHtml(item.text)}</p>
`
)
.join('')
}
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
function renderDistribution(items = []) {
if (!items.length) return ''
return `<ul>${items
.map(
(item) => `
<li>
<strong>${escapeHtml(item[0])}</strong>${escapeHtml(item[1])}
<p>${escapeHtml(item[2])}</p>
</li>
`
)
.join('')}</ul>`
}
function renderResultTable(rows = []) {
if (!rows.length) return ''
return `
<table>
<thead>
<tr>
<th>核心指标</th>
<th>项目前</th>
<th>项目后 / 峰值</th>
<th>变化说明</th>
</tr>
</thead>
<tbody>
${rows
.map(
(row) => `
<tr>
<td>${escapeHtml(row[0])}</td>
<td>${escapeHtml(row[1])}</td>
<td><strong>${escapeHtml(row[2])}</strong></td>
<td>${escapeHtml(row[3])}</td>
</tr>
`
)
.join('')}
</tbody>
</table>
`
}
function buildCaseArticleHtml(caseData) {
return `
<h2>项目背景</h2>
${renderParagraphs(caseData.background)}
<h2>客户希望解决什么</h2>
${renderList(caseData.objectives)}
<h2>前期诊断结果</h2>
<p>项目启动前,我们先围绕真实用户提问、品牌信息完整度、平台推荐表现与转化承接路径做了一轮基线评估。</p>
${renderKeyValueList(caseData.baseline)}
<h2>解决方案</h2>
<p>方案不只是补充内容,而是围绕 AI 能理解、能引用、能推荐的结构,重新组织品牌事实、用户问题和可信信源。</p>
${renderQuestionFramework(caseData.questionFramework)}
<h2>内容建设节奏</h2>
${renderPhases(caseData.phases)}
2026-07-16 16:42:45 +08:00
2026-07-21 11:09:17 +08:00
<h2>可信信源分发</h2>
${renderDistribution(caseData.distribution)}
<h2>项目成果</h2>
<p>以下指标用于展示项目推进前后的变化,不代表对其他项目作出相同效果承诺。</p>
${renderResultTable(caseData.resultTable)}
`
}
</script>
<template>
<div class="case-news-detail news-detail-page">
<a class="skip-link" href="#main-content">跳到主要内容</a>
<SiteHeader />
<main id="main-content" class="news-detail-main">
<div class="news-detail-shell">
<nav class="news-breadcrumb" aria-label="面包屑">
<NuxtLink to="/">首页</NuxtLink>
<span>/</span>
<NuxtLink to="/cases">客户案例</NuxtLink>
<span>/</span>
<b>{{ articleTag }}</b>
</nav>
<div class="news-detail-layout">
<article class="news-article">
<header class="news-article-header">
<h1>{{ articleTitle }}</h1>
<p class="news-excerpt">{{ caseStudy.description || caseStudy.summary }}</p>
<div class="news-meta">
<span>{{ caseStudy.client }}</span>
<span>{{ articleTag }}</span>
2026-07-16 16:42:45 +08:00
</div>
2026-07-21 11:09:17 +08:00
<div class="news-tag-row">
<span v-for="tag in caseTags" :key="tag">{{ tag }}</span>
</div>
</header>
<section class="news-consult-banner" aria-label="方案咨询">
<p>
<span>GEO重构流量入口让Al诊断您的品牌</span>
<span>事件影响力量化评估品牌价值</span>
</p>
<button type="button" @click="requestPlanConsultation">方案咨询</button>
</section>
<figure v-if="caseStudy.cover" class="news-cover">
<img :src="caseStudy.cover" :alt="articleTitle" />
</figure>
<UEditor :content="articleHtml" />
</article>
<aside class="news-sidebar" aria-label="相关推荐">
<section v-if="relatedCases.length" class="sidebar-panel">
2026-07-21 11:09:17 +08:00
<h2>相关案例</h2>
<ol class="hot-list">
<li v-for="(item, index) in relatedCases" :key="item.id || item.slug">
<NuxtLink :to="caseListLink(item)">
2026-07-21 11:09:17 +08:00
<span>{{ index + 1 }}</span>
<strong>{{ item.title }}</strong>
<em>{{ item.metric }}</em>
</NuxtLink>
</li>
</ol>
</section>
2026-07-16 16:42:45 +08:00
<section v-if="recommendedCases.length" class="sidebar-panel">
2026-07-21 11:09:17 +08:00
<h2>推荐案例</h2>
<div class="recommend-list">
<NuxtLink
v-for="item in recommendedCases"
:key="item.id || item.slug"
:to="`/cases/${item.slug}`"
>
2026-07-21 11:09:17 +08:00
<img :src="item.cover" :alt="item.title" />
<span>
<strong>{{ item.title }}</strong>
<small>{{ item.secondary }}</small>
</span>
</NuxtLink>
</div>
</section>
</aside>
2026-07-16 16:42:45 +08:00
</div>
2026-07-21 11:09:17 +08:00
</div>
2026-07-16 16:42:45 +08:00
</main>
<SiteFooter />
</div>
</template>
<style scoped>
2026-07-21 11:09:17 +08:00
.news-detail-page {
min-height: 100vh;
color: #132033;
background:
radial-gradient(circle at 90% 0, rgba(73, 144, 255, 0.12), transparent 320px),
linear-gradient(180deg, #f7fbff 0, #fff 330px);
2026-07-16 16:42:45 +08:00
}
.skip-link {
position: fixed;
z-index: 100;
2026-07-21 11:09:17 +08:00
top: 12px;
left: 12px;
2026-07-16 16:42:45 +08:00
padding: 10px 14px;
2026-07-21 11:09:17 +08:00
border-radius: 8px;
2026-07-16 16:42:45 +08:00
color: #fff;
2026-07-21 11:09:17 +08:00
background: #1677ff;
2026-07-16 16:42:45 +08:00
transform: translateY(-150%);
2026-07-21 11:09:17 +08:00
transition: transform 0.2s ease;
2026-07-16 16:42:45 +08:00
}
.skip-link:focus {
transform: translateY(0);
}
2026-07-21 11:09:17 +08:00
.news-detail-main {
padding: 42px 0 96px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-detail-shell {
width: min(1296px, calc(100% - 48px));
margin: 0 auto;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-breadcrumb {
2026-07-16 16:42:45 +08:00
display: flex;
2026-07-21 11:09:17 +08:00
flex-wrap: wrap;
2026-07-16 16:42:45 +08:00
align-items: center;
gap: 10px;
2026-07-21 11:09:17 +08:00
color: #718197;
font-size: 14px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-breadcrumb a {
color: #61738b;
2026-07-16 16:42:45 +08:00
text-decoration: none;
}
2026-07-21 11:09:17 +08:00
.news-breadcrumb a:hover,
.news-breadcrumb b {
color: #1677ff;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-detail-layout {
2026-07-16 16:42:45 +08:00
display: grid;
2026-07-21 11:09:17 +08:00
grid-template-columns: minmax(0, 880px) 320px;
2026-07-16 16:42:45 +08:00
align-items: start;
2026-07-21 11:09:17 +08:00
gap: 72px;
margin-top: 34px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-article {
min-width: 0;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-article-header {
padding-bottom: 34px;
border-bottom: 1px solid #e6edf6;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-kicker {
margin: 0;
color: #1677ff;
font-size: 14px;
font-weight: 700;
letter-spacing: 0.08em;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-article h1 {
2026-07-16 16:42:45 +08:00
margin: 18px 0 0;
2026-07-21 11:09:17 +08:00
color: #111d2f;
font-size: clamp(30px, 3vw, 44px);
font-weight: 600;
letter-spacing: -0.035em;
line-height: 1.38;
text-wrap: pretty;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-excerpt {
2026-07-16 16:42:45 +08:00
max-width: 820px;
2026-07-21 11:09:17 +08:00
margin: 24px 0 0;
color: #6c7b90;
font-size: 16px;
line-height: 1.9;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-meta {
display: flex;
flex-wrap: wrap;
gap: 18px 28px;
margin-top: 26px;
color: #7b8da3;
2026-07-16 16:42:45 +08:00
font-size: 14px;
2026-07-21 11:09:17 +08:00
font-variant-numeric: tabular-nums;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-tag-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 18px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-tag-row span {
padding: 5px 10px;
color: #6b7f96;
background: #eef5fd;
font-size: 13px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-consult-banner {
2026-07-16 16:42:45 +08:00
display: grid;
2026-07-21 11:09:17 +08:00
grid-template-columns: minmax(0, 1fr) auto;
2026-07-16 16:42:45 +08:00
align-items: center;
gap: 28px;
2026-07-21 11:09:17 +08:00
margin: 34px 0 38px;
padding: 28px 34px;
overflow: hidden;
background:
radial-gradient(circle at 8% 20%, rgba(255, 255, 255, 0.28), transparent 24%),
linear-gradient(105deg, #0f63ff 0%, #08b9d8 100%);
color: #fff;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-consult-banner p {
2026-07-16 16:42:45 +08:00
margin: 0;
2026-07-21 11:09:17 +08:00
font-size: clamp(18px, 2vw, 24px);
2026-07-16 16:42:45 +08:00
font-weight: 700;
2026-07-21 11:09:17 +08:00
letter-spacing: -0.025em;
line-height: 1.48;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-consult-banner span {
2026-07-16 16:42:45 +08:00
display: block;
}
2026-07-21 11:09:17 +08:00
.news-consult-banner button {
min-width: 136px;
height: 48px;
padding: 0 28px;
border: 0;
background: #fff;
color: #1677ff;
cursor: pointer;
font-size: 16px;
2026-07-16 16:42:45 +08:00
font-weight: 700;
2026-07-21 11:09:17 +08:00
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-consult-banner button:hover {
transform: translateY(-2px);
box-shadow: 0 14px 26px rgba(4, 67, 152, 0.24);
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-cover {
margin: 42px 0 44px;
overflow: hidden;
border-radius: 10px;
background: #eef5fd;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-cover img {
width: 100%;
max-height: 420px;
2026-07-16 16:42:45 +08:00
display: block;
2026-07-21 11:09:17 +08:00
object-fit: cover;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-article :deep(.ueditor-content) {
color: #27364a;
font-size: 16px;
line-height: 2;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-article :deep(.ueditor-content h2) {
2026-07-16 16:42:45 +08:00
margin-top: 58px;
2026-07-21 11:09:17 +08:00
color: #111d2f;
font-size: 25px;
line-height: 1.45;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-article :deep(.ueditor-content h3) {
margin-top: 34px;
font-size: 20px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-article :deep(.ueditor-content blockquote) {
margin: 36px 0;
border-left: 4px solid #d9e8fb;
border-radius: 0;
color: #53657c;
background: transparent;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-article :deep(.ueditor-content table) {
2026-07-16 16:42:45 +08:00
font-size: 14px;
}
2026-07-21 11:09:17 +08:00
.news-sidebar {
position: sticky;
top: 88px;
2026-07-16 16:42:45 +08:00
display: grid;
2026-07-21 11:09:17 +08:00
gap: 34px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.sidebar-panel h2 {
position: relative;
margin: 0 0 18px;
color: #111d2f;
font-size: 16px;
font-weight: 700;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.sidebar-panel h2::after {
color: #ff7a1a;
content: '▼';
font-size: 10px;
margin-left: 2px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.hot-list {
display: grid;
gap: 1px;
margin: 0;
padding: 0;
list-style: none;
background: #f5f9fd;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.hot-list a {
2026-07-16 16:42:45 +08:00
display: grid;
2026-07-21 11:09:17 +08:00
grid-template-columns: 28px minmax(0, 1fr) auto;
2026-07-16 16:42:45 +08:00
align-items: center;
2026-07-21 11:09:17 +08:00
gap: 10px;
padding: 13px 14px;
color: #172236;
text-decoration: none;
background: #f8fbff;
transition:
color 0.2s ease,
background 0.2s ease,
transform 0.2s ease;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.hot-list a:hover {
color: #1677ff;
background: #eef6ff;
transform: translateX(2px);
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.hot-list span {
color: #ff8a2a;
font-weight: 700;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.hot-list strong {
overflow: hidden;
2026-07-16 16:42:45 +08:00
font-size: 14px;
2026-07-21 11:09:17 +08:00
font-weight: 500;
text-overflow: ellipsis;
white-space: nowrap;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.hot-list em {
color: #1677ff;
font-size: 13px;
font-style: normal;
font-weight: 700;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.tag-cloud {
2026-07-16 16:42:45 +08:00
display: flex;
2026-07-21 11:09:17 +08:00
flex-wrap: wrap;
gap: 12px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.tag-cloud span {
padding: 9px 15px;
border: 1px solid #e2eaf4;
color: #27364a;
background: #fff;
font-size: 14px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.recommend-list {
2026-07-16 16:42:45 +08:00
display: grid;
2026-07-21 11:09:17 +08:00
gap: 15px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.recommend-list a {
display: grid;
grid-template-columns: 82px minmax(0, 1fr);
gap: 12px;
color: #172236;
text-decoration: none;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.recommend-list img {
width: 82px;
height: 56px;
2026-07-16 16:42:45 +08:00
object-fit: cover;
2026-07-21 11:09:17 +08:00
background: #eef5fd;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.recommend-list strong {
display: -webkit-box;
2026-07-16 16:42:45 +08:00
overflow: hidden;
2026-07-21 11:09:17 +08:00
font-size: 14px;
font-weight: 500;
line-height: 1.5;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.recommend-list small {
display: block;
margin-top: 6px;
color: #7d8da1;
font-size: 13px;
line-height: 1.5;
2026-07-16 16:42:45 +08:00
}
@media (max-width: 980px) {
2026-07-21 11:09:17 +08:00
.news-detail-layout {
2026-07-16 16:42:45 +08:00
grid-template-columns: 1fr;
2026-07-21 11:09:17 +08:00
gap: 48px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-sidebar {
2026-07-16 16:42:45 +08:00
position: static;
}
}
2026-07-21 11:09:17 +08:00
@media (max-width: 640px) {
.news-detail-main {
padding: 28px 0 68px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-detail-shell {
width: min(100% - 24px, 560px);
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-detail-layout {
margin-top: 24px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-article h1 {
font-size: 28px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-consult-banner {
2026-07-16 16:42:45 +08:00
grid-template-columns: 1fr;
2026-07-21 11:09:17 +08:00
gap: 18px;
margin: 28px 0 32px;
padding: 24px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-consult-banner button {
width: 100%;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.news-cover img {
max-height: 260px;
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.hot-list a {
grid-template-columns: 24px minmax(0, 1fr);
2026-07-16 16:42:45 +08:00
}
2026-07-21 11:09:17 +08:00
.hot-list em {
2026-07-16 16:42:45 +08:00
display: none;
}
}
</style>