2ff1064de7
这一提交完成了多项功能升级与优化: 1. 新增全局浮动客服组件,集成腾讯云TRTC聊天能力并添加错误格式化工具 2. 新增图片资源并调整部分静态文件结构 3. 优化案例页面:重构筛选逻辑、新增空状态处理、添加加载更多功能 4. 优化首页布局:修复统计数字动画、调整移动端适配样式、优化轮播逻辑 5. 重构官方内容获取工具:添加缓存机制、优化媒体资源处理 6. 优化API代理配置,移除默认本地代理并新增环境变量配置 7. 新增页面数据恢复刷新逻辑,优化页面交互体验 8. 修复按钮组件冗余代码,调整头部导航移动端样式
619 lines
15 KiB
Vue
619 lines
15 KiB
Vue
<script setup>
|
||
import SiteFooter from '~/components/site/SiteFooter.vue'
|
||
import SiteHeader from '~/components/site/SiteHeader.vue'
|
||
import UEditor from '~/components/UEditor.vue'
|
||
|
||
const route = useRoute()
|
||
const { openConsultation } = useConsultation()
|
||
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
|
||
}
|
||
})
|
||
|
||
if (!pageData.value?.current) {
|
||
throw createError({ statusCode: 404, statusMessage: '案例不存在' })
|
||
}
|
||
|
||
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)
|
||
)
|
||
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'
|
||
}
|
||
|
||
useHead(() => ({
|
||
title: `${articleTitle.value}|客户案例`,
|
||
meta: [
|
||
{
|
||
name: 'description',
|
||
content:
|
||
caseStudy.value.seoDescription ||
|
||
caseStudy.value.description ||
|
||
caseStudy.value.summary ||
|
||
articleTitle.value
|
||
},
|
||
{ property: 'og:title', content: articleTitle.value },
|
||
{ property: 'og:description', content: caseStudy.value.description || caseStudy.value.summary },
|
||
{ property: 'og:image', content: caseStudy.value.cover }
|
||
]
|
||
}))
|
||
|
||
function requestPlanConsultation() {
|
||
openConsultation({
|
||
title: '方案咨询',
|
||
industry: caseStudy.value.industry,
|
||
need: articleTitle.value,
|
||
source: '客户案例详情页 CTA'
|
||
})
|
||
}
|
||
|
||
function escapeHtml(value) {
|
||
return String(value ?? '')
|
||
.replaceAll('&', '&')
|
||
.replaceAll('<', '<')
|
||
.replaceAll('>', '>')
|
||
.replaceAll('"', '"')
|
||
.replaceAll("'", ''')
|
||
}
|
||
|
||
function renderParagraphs(paragraphs = []) {
|
||
return paragraphs.map((paragraph) => `<p>${escapeHtml(paragraph)}</p>`).join('')
|
||
}
|
||
|
||
function renderList(items = []) {
|
||
if (!items.length) return ''
|
||
|
||
return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>`
|
||
}
|
||
|
||
function renderKeyValueList(items = []) {
|
||
if (!items.length) return ''
|
||
|
||
return `<ul>${items
|
||
.map((item) => `<li><strong>${escapeHtml(item[0])}</strong>:${escapeHtml(item[1])}</li>`)
|
||
.join('')}</ul>`
|
||
}
|
||
|
||
function renderQuestionFramework(items = []) {
|
||
if (!items.length) return ''
|
||
|
||
return items
|
||
.map(
|
||
(item) => `
|
||
<h3>${escapeHtml(item.title)}</h3>
|
||
<p>${escapeHtml(item.content)}</p>
|
||
<blockquote>${escapeHtml(item.goal)}</blockquote>
|
||
`
|
||
)
|
||
.join('')
|
||
}
|
||
|
||
function renderPhases(items = []) {
|
||
if (!items.length) return ''
|
||
|
||
return items
|
||
.map(
|
||
(item) => `
|
||
<h3>${escapeHtml(item.number)}|${escapeHtml(item.title)}</h3>
|
||
<p>${escapeHtml(item.text)}</p>
|
||
`
|
||
)
|
||
.join('')
|
||
}
|
||
|
||
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)}
|
||
|
||
<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>
|
||
</div>
|
||
<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">
|
||
<h2>相关案例</h2>
|
||
<ol class="hot-list">
|
||
<li v-for="(item, index) in relatedCases" :key="item.id || item.slug">
|
||
<NuxtLink :to="caseListLink(item)">
|
||
<span>{{ index + 1 }}</span>
|
||
<strong>{{ item.title }}</strong>
|
||
<em>{{ item.metric }}</em>
|
||
</NuxtLink>
|
||
</li>
|
||
</ol>
|
||
</section>
|
||
|
||
<section v-if="recommendedCases.length" class="sidebar-panel">
|
||
<h2>推荐案例</h2>
|
||
<div class="recommend-list">
|
||
<NuxtLink
|
||
v-for="item in recommendedCases"
|
||
:key="item.id || item.slug"
|
||
:to="`/cases/${item.slug}`"
|
||
>
|
||
<img :src="item.cover" :alt="item.title" />
|
||
<span>
|
||
<strong>{{ item.title }}</strong>
|
||
<small>{{ item.secondary }}</small>
|
||
</span>
|
||
</NuxtLink>
|
||
</div>
|
||
</section>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<SiteFooter />
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.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);
|
||
}
|
||
.skip-link {
|
||
position: fixed;
|
||
z-index: 100;
|
||
top: 12px;
|
||
left: 12px;
|
||
padding: 10px 14px;
|
||
border-radius: 8px;
|
||
color: #fff;
|
||
background: #1677ff;
|
||
transform: translateY(-150%);
|
||
transition: transform 0.2s ease;
|
||
}
|
||
.skip-link:focus {
|
||
transform: translateY(0);
|
||
}
|
||
.news-detail-main {
|
||
padding: 42px 0 96px;
|
||
}
|
||
.news-detail-shell {
|
||
width: min(1296px, calc(100% - 48px));
|
||
margin: 0 auto;
|
||
}
|
||
.news-breadcrumb {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 10px;
|
||
color: #718197;
|
||
font-size: 14px;
|
||
}
|
||
.news-breadcrumb a {
|
||
color: #61738b;
|
||
text-decoration: none;
|
||
}
|
||
.news-breadcrumb a:hover,
|
||
.news-breadcrumb b {
|
||
color: #1677ff;
|
||
}
|
||
.news-detail-layout {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 880px) 320px;
|
||
align-items: start;
|
||
gap: 72px;
|
||
margin-top: 34px;
|
||
}
|
||
.news-article {
|
||
min-width: 0;
|
||
}
|
||
.news-article-header {
|
||
padding-bottom: 34px;
|
||
border-bottom: 1px solid #e6edf6;
|
||
}
|
||
.news-kicker {
|
||
margin: 0;
|
||
color: #1677ff;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
.news-article h1 {
|
||
margin: 18px 0 0;
|
||
color: #111d2f;
|
||
font-size: clamp(30px, 3vw, 44px);
|
||
font-weight: 600;
|
||
letter-spacing: -0.035em;
|
||
line-height: 1.38;
|
||
text-wrap: pretty;
|
||
}
|
||
.news-excerpt {
|
||
max-width: 820px;
|
||
margin: 24px 0 0;
|
||
color: #6c7b90;
|
||
font-size: 16px;
|
||
line-height: 1.9;
|
||
}
|
||
.news-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 18px 28px;
|
||
margin-top: 26px;
|
||
color: #7b8da3;
|
||
font-size: 14px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.news-tag-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-top: 18px;
|
||
}
|
||
.news-tag-row span {
|
||
padding: 5px 10px;
|
||
color: #6b7f96;
|
||
background: #eef5fd;
|
||
font-size: 13px;
|
||
}
|
||
.news-consult-banner {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
align-items: center;
|
||
gap: 28px;
|
||
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;
|
||
}
|
||
.news-consult-banner p {
|
||
margin: 0;
|
||
font-size: clamp(18px, 2vw, 24px);
|
||
font-weight: 700;
|
||
letter-spacing: -0.025em;
|
||
line-height: 1.48;
|
||
}
|
||
.news-consult-banner span {
|
||
display: block;
|
||
}
|
||
.news-consult-banner button {
|
||
min-width: 136px;
|
||
height: 48px;
|
||
padding: 0 28px;
|
||
border: 0;
|
||
background: #fff;
|
||
color: #1677ff;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
transition:
|
||
transform 0.2s ease,
|
||
box-shadow 0.2s ease;
|
||
}
|
||
.news-consult-banner button:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 14px 26px rgba(4, 67, 152, 0.24);
|
||
}
|
||
.news-cover {
|
||
margin: 42px 0 44px;
|
||
overflow: hidden;
|
||
border-radius: 10px;
|
||
background: #eef5fd;
|
||
}
|
||
.news-cover img {
|
||
width: 100%;
|
||
max-height: 420px;
|
||
display: block;
|
||
object-fit: cover;
|
||
}
|
||
.news-article :deep(.ueditor-content) {
|
||
color: #27364a;
|
||
font-size: 16px;
|
||
line-height: 2;
|
||
}
|
||
.news-article :deep(.ueditor-content h2) {
|
||
margin-top: 58px;
|
||
color: #111d2f;
|
||
font-size: 25px;
|
||
line-height: 1.45;
|
||
}
|
||
.news-article :deep(.ueditor-content h3) {
|
||
margin-top: 34px;
|
||
font-size: 20px;
|
||
}
|
||
.news-article :deep(.ueditor-content blockquote) {
|
||
margin: 36px 0;
|
||
border-left: 4px solid #d9e8fb;
|
||
border-radius: 0;
|
||
color: #53657c;
|
||
background: transparent;
|
||
}
|
||
.news-article :deep(.ueditor-content table) {
|
||
font-size: 14px;
|
||
}
|
||
.news-sidebar {
|
||
position: sticky;
|
||
top: 88px;
|
||
display: grid;
|
||
gap: 34px;
|
||
}
|
||
.sidebar-panel h2 {
|
||
position: relative;
|
||
margin: 0 0 18px;
|
||
color: #111d2f;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
}
|
||
.sidebar-panel h2::after {
|
||
color: #ff7a1a;
|
||
content: '▼';
|
||
font-size: 10px;
|
||
margin-left: 2px;
|
||
}
|
||
.hot-list {
|
||
display: grid;
|
||
gap: 1px;
|
||
margin: 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
background: #f5f9fd;
|
||
}
|
||
.hot-list a {
|
||
display: grid;
|
||
grid-template-columns: 28px minmax(0, 1fr) auto;
|
||
align-items: center;
|
||
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;
|
||
}
|
||
.hot-list a:hover {
|
||
color: #1677ff;
|
||
background: #eef6ff;
|
||
transform: translateX(2px);
|
||
}
|
||
.hot-list span {
|
||
color: #ff8a2a;
|
||
font-weight: 700;
|
||
}
|
||
.hot-list strong {
|
||
overflow: hidden;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.hot-list em {
|
||
color: #1677ff;
|
||
font-size: 13px;
|
||
font-style: normal;
|
||
font-weight: 700;
|
||
}
|
||
.tag-cloud {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
.tag-cloud span {
|
||
padding: 9px 15px;
|
||
border: 1px solid #e2eaf4;
|
||
color: #27364a;
|
||
background: #fff;
|
||
font-size: 14px;
|
||
}
|
||
.recommend-list {
|
||
display: grid;
|
||
gap: 15px;
|
||
}
|
||
.recommend-list a {
|
||
display: grid;
|
||
grid-template-columns: 82px minmax(0, 1fr);
|
||
gap: 12px;
|
||
color: #172236;
|
||
text-decoration: none;
|
||
}
|
||
.recommend-list img {
|
||
width: 82px;
|
||
height: 56px;
|
||
object-fit: cover;
|
||
background: #eef5fd;
|
||
}
|
||
.recommend-list strong {
|
||
display: -webkit-box;
|
||
overflow: hidden;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
line-height: 1.5;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 2;
|
||
}
|
||
.recommend-list small {
|
||
display: block;
|
||
margin-top: 6px;
|
||
color: #7d8da1;
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
}
|
||
@media (max-width: 980px) {
|
||
.news-detail-layout {
|
||
grid-template-columns: 1fr;
|
||
gap: 48px;
|
||
}
|
||
.news-sidebar {
|
||
position: static;
|
||
}
|
||
}
|
||
@media (max-width: 640px) {
|
||
.news-detail-main {
|
||
padding: 28px 0 68px;
|
||
}
|
||
.news-detail-shell {
|
||
width: min(100% - 24px, 560px);
|
||
}
|
||
.news-detail-layout {
|
||
margin-top: 24px;
|
||
}
|
||
.news-article h1 {
|
||
font-size: 28px;
|
||
}
|
||
.news-consult-banner {
|
||
grid-template-columns: 1fr;
|
||
gap: 18px;
|
||
margin: 28px 0 32px;
|
||
padding: 24px;
|
||
}
|
||
.news-consult-banner button {
|
||
width: 100%;
|
||
}
|
||
.news-cover img {
|
||
max-height: 260px;
|
||
}
|
||
.hot-list a {
|
||
grid-template-columns: 24px minmax(0, 1fr);
|
||
}
|
||
.hot-list em {
|
||
display: none;
|
||
}
|
||
}
|
||
</style>
|